1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.annotation.TargetApi;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.appwidget.AppWidgetHostView;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentCallbacks2;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.SharedPreferences;
44 import android.content.pm.ActivityInfo;
45 import android.content.pm.ApplicationInfo;
46 import android.content.pm.PackageManager;
47 import android.content.pm.PackageManager.NameNotFoundException;
48 import android.content.res.Configuration;
49 import android.database.ContentObserver;
50 import android.database.sqlite.SQLiteDatabase;
51 import android.graphics.Bitmap;
52 import android.graphics.Canvas;
53 import android.graphics.Color;
54 import android.graphics.PorterDuff;
55 import android.graphics.Rect;
56 import android.graphics.drawable.Drawable;
57 import android.net.Uri;
58 import android.os.AsyncTask;
59 import android.os.Build;
60 import android.os.Bundle;
61 import android.os.Environment;
62 import android.os.Handler;
63 import android.os.Message;
64 import android.os.StrictMode;
65 import android.os.SystemClock;
66 import android.text.Selection;
67 import android.text.SpannableStringBuilder;
68 import android.text.TextUtils;
69 import android.text.method.TextKeyListener;
70 import android.util.Log;
71 import android.view.Display;
72 import android.view.Gravity;
73 import android.view.HapticFeedbackConstants;
74 import android.view.KeyEvent;
75 import android.view.LayoutInflater;
76 import android.view.Menu;
77 import android.view.MotionEvent;
78 import android.view.Surface;
79 import android.view.View;
80 import android.view.View.OnClickListener;
81 import android.view.View.OnLongClickListener;
82 import android.view.ViewGroup;
83 import android.view.ViewStub;
84 import android.view.ViewTreeObserver;
85 import android.view.Window;
86 import android.view.WindowManager;
87 import android.view.accessibility.AccessibilityEvent;
88 import android.view.inputmethod.InputMethodManager;
89 import android.widget.Advanceable;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.TextView;
93 import android.widget.Toast;
94
95 import com.android.launcher3.DropTarget.DragObject;
96 import com.android.launcher3.PagedView.PageSwitchListener;
97 import com.android.launcher3.compat.AppWidgetManagerCompat;
98 import com.android.launcher3.compat.LauncherActivityInfoCompat;
99 import com.android.launcher3.compat.LauncherAppsCompat;
100 import com.android.launcher3.compat.PackageInstallerCompat;
101 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
102 import com.android.launcher3.compat.UserHandleCompat;
103 import com.android.launcher3.compat.UserManagerCompat;
104
105 import java.io.DataInputStream;
106 import java.io.DataOutputStream;
107 import java.io.File;
108 import java.io.FileDescriptor;
109 import java.io.FileNotFoundException;
110 import java.io.FileOutputStream;
111 import java.io.IOException;
112 import java.io.PrintWriter;
113 import java.lang.reflect.InvocationTargetException;
114 import java.lang.reflect.Method;
115 import java.text.DateFormat;
116 import java.util.ArrayList;
117 import java.util.Collection;
118 import java.util.Date;
119 import java.util.HashMap;
120 import java.util.HashSet;
121 import java.util.List;
122 import java.util.concurrent.atomic.AtomicInteger;
123
124 /**
125 * Default launcher application.
126 */
127 public class Launcher extends Activity
128 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
129 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
130 LauncherStateTransitionAnimation.Callbacks {
131 static final String TAG = "Launcher";
132 static final boolean LOGD = false;
133
134 static final boolean PROFILE_STARTUP = false;
135 static final boolean DEBUG_WIDGETS = false;
136 static final boolean DEBUG_STRICT_MODE = false;
137 static final boolean DEBUG_RESUME_TIME = false;
138 static final boolean DEBUG_DUMP_LOG = false;
139
140 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
141
142 private static final int REQUEST_CREATE_SHORTCUT = 1;
143 private static final int REQUEST_CREATE_APPWIDGET = 5;
144 private static final int REQUEST_PICK_APPWIDGET = 9;
145 private static final int REQUEST_PICK_WALLPAPER = 10;
146
147 private static final int REQUEST_BIND_APPWIDGET = 11;
148 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
149
150 /**
151 * IntentStarter uses request codes starting with this. This must be greater than all activity
152 * request codes used internally.
153 */
154 protected static final int REQUEST_LAST = 100;
155
156 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
157
158 static final int SCREEN_COUNT = 5;
159
160 // To turn on these properties, type
161 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
162 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
163
164 // The Intent extra that defines whether to ignore the launch animation
165 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
166 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
167
168 // Type: int
169 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
170 // Type: int
171 private static final String RUNTIME_STATE = "launcher.state";
172 // Type: int
173 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
174 // Type: int
175 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
176 // Type: int
177 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
178 // Type: int
179 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
180 // Type: boolean
181 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
182 // Type: long
183 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
184 // Type: int
185 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
186 // Type: int
187 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
188 // Type: parcelable
189 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
190 // Type: parcelable
191 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
192 // Type: int[]
193 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
194
195 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
196 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
197
198 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
199 static final String ACTION_FIRST_LOAD_COMPLETE =
200 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
201
202 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
203 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
204
205 private static final String QSB_WIDGET_ID = "qsb_widget_id";
206 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
207
208 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
209
210 /** The different states that Launcher can be in. */
211 enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED };
212 private State mState = State.WORKSPACE;
213 private AnimatorSet mStateAnimation;
214 private LauncherStateTransitionAnimation mStateTransitionAnimation;
215
216 private boolean mIsSafeModeEnabled;
217
218 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
219 LauncherOverlay mLauncherOverlay;
220 InsettableFrameLayout mLauncherOverlayContainer;
221
222 static final int APPWIDGET_HOST_ID = 1024;
223 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
224 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
225 private static final int ACTIVITY_START_DELAY = 1000;
226
227 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
228 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
229
230 // How long to wait before the new-shortcut animation automatically pans the workspace
231 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
232 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
233 private static int NEW_APPS_ANIMATION_DELAY = 500;
234
235 private final BroadcastReceiver mCloseSystemDialogsReceiver
236 = new CloseSystemDialogsIntentReceiver();
237 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
238
239 private LayoutInflater mInflater;
240
241 private Workspace mWorkspace;
242 private View mLauncherView;
243 private View mPageIndicators;
244 private DragLayer mDragLayer;
245 private DragController mDragController;
246 private View mWeightWatcher;
247
248 private AppWidgetManagerCompat mAppWidgetManager;
249 private LauncherAppWidgetHost mAppWidgetHost;
250
251 private ItemInfo mPendingAddInfo = new ItemInfo();
252 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
253 private int mPendingAddWidgetId = -1;
254
255 private int[] mTmpAddItemCellCoordinates = new int[2];
256
257 private FolderInfo mFolderInfo;
258
259 private Hotseat mHotseat;
260 private ViewGroup mOverviewPanel;
261
262 private View mAllAppsButton;
263
264 private SearchDropTargetBar mSearchDropTargetBar;
265 private AppsContainerView mAppsView;
266 private AppsCustomizeTabHost mAppsCustomizeTabHost;
267 private AppsCustomizePagedView mAppsCustomizeContent;
268 private boolean mAutoAdvanceRunning = false;
269 private AppWidgetHostView mQsb;
270
271 private Bundle mSavedState;
272 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
273 // scroll issues (because the workspace may not have been measured yet) and extra work.
274 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
275 private State mOnResumeState = State.NONE;
276
277 private SpannableStringBuilder mDefaultKeySsb = null;
278
279 private boolean mWorkspaceLoading = true;
280
281 private boolean mPaused = true;
282 private boolean mRestoring;
283 private boolean mWaitingForResult;
284 private boolean mOnResumeNeedsLoad;
285
286 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
287 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
288
289 private Bundle mSavedInstanceState;
290
291 private LauncherModel mModel;
292 private IconCache mIconCache;
293 private boolean mUserPresent = true;
294 private boolean mVisible = false;
295 private boolean mHasFocus = false;
296 private boolean mAttached = false;
297
298 private static LocaleConfiguration sLocaleConfiguration = null;
299
300 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
301
302 private View.OnTouchListener mHapticFeedbackTouchListener;
303
304 // Related to the auto-advancing of widgets
305 private final int ADVANCE_MSG = 1;
306 private final int mAdvanceInterval = 20000;
307 private final int mAdvanceStagger = 250;
308 private long mAutoAdvanceSentTime;
309 private long mAutoAdvanceTimeLeft = -1;
310 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
311 new HashMap<View, AppWidgetProviderInfo>();
312
313 // Determines how long to wait after a rotation before restoring the screen orientation to
314 // match the sensor state.
315 private final int mRestoreScreenOrientationDelay = 500;
316
317 private Drawable mWorkspaceBackgroundDrawable;
318
319 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
320 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
321
322 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
323 static Date sDateStamp = new Date();
324 static DateFormat sDateFormat =
325 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
326 static long sRunStart = System.currentTimeMillis();
327 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
328
329 // We only want to get the SharedPreferences once since it does an FS stat each time we get
330 // it from the context.
331 private SharedPreferences mSharedPrefs;
332
333 // Holds the page that we need to animate to, and the icon views that we need to animate up
334 // when we scroll to that page on resume.
335 private ImageView mFolderIconImageView;
336 private Bitmap mFolderIconBitmap;
337 private Canvas mFolderIconCanvas;
338 private Rect mRectForFolderAnimation = new Rect();
339
340 private BubbleTextView mWaitingForResume;
341
342 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
343 new HashMap<String, CustomAppWidget>();
344
345 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
346 static {
347 if (ENABLE_CUSTOM_WIDGET_TEST) {
348 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
349 }
350 }
351
352 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
353 private static Method sClipRevealMethod = null;
354 static {
355 Class<?> activityOptionsClass = ActivityOptions.class;
356 try {
357 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
358 View.class, int.class, int.class, int.class, int.class);
359 } catch (Exception e) {
360 // Earlier version
361 }
362 }
363
364 private Runnable mBuildLayersRunnable = new Runnable() {
365 public void run() {
366 if (mWorkspace != null) {
367 mWorkspace.buildPageHardwareLayers();
368 }
369 }
370 };
371
372 private static PendingAddArguments sPendingAddItem;
373
374 private static class PendingAddArguments {
375 int requestCode;
376 Intent intent;
377 long container;
378 long screenId;
379 int cellX;
380 int cellY;
381 int appWidgetId;
382 }
383
384 private Stats mStats;
385
386 FocusIndicatorView mFocusHandler;
387
388 @Override
389 protected void onCreate(Bundle savedInstanceState) {
390 if (DEBUG_STRICT_MODE) {
391 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
392 .detectDiskReads()
393 .detectDiskWrites()
394 .detectNetwork() // or .detectAll() for all detectable problems
395 .penaltyLog()
396 .build());
397 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
398 .detectLeakedSqlLiteObjects()
399 .detectLeakedClosableObjects()
400 .penaltyLog()
401 .penaltyDeath()
402 .build());
403 }
404
405 if (mLauncherCallbacks != null) {
406 mLauncherCallbacks.preOnCreate();
407 }
408
409 super.onCreate(savedInstanceState);
410
411 LauncherAppState.setApplicationContext(getApplicationContext());
412 LauncherAppState app = LauncherAppState.getInstance();
413 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
414
415 // Lazy-initialize the dynamic grid
416 DeviceProfile grid = app.initDynamicGrid(this);
417
418 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
419 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
420 Context.MODE_PRIVATE);
421 mIsSafeModeEnabled = getPackageManager().isSafeMode();
422 mModel = app.setLauncher(this);
423 mIconCache = app.getIconCache();
424 mIconCache.flushInvalidIcons(grid);
425 mDragController = new DragController(this);
426 mInflater = getLayoutInflater();
427 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
428
429 mStats = new Stats(this);
430
431 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
432
433 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
434 mAppWidgetHost.startListening();
435
436 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
437 // this also ensures that any synchronous binding below doesn't re-trigger another
438 // LauncherModel load.
439 mPaused = false;
440
441 if (PROFILE_STARTUP) {
442 android.os.Debug.startMethodTracing(
443 Environment.getExternalStorageDirectory() + "/launcher");
444 }
445
446 checkForLocaleChange();
447 setContentView(R.layout.launcher);
448
449 setupViews();
450 grid.layout(this);
451
452 registerContentObservers();
453
454 lockAllApps();
455
456 mSavedState = savedInstanceState;
457 restoreState(mSavedState);
458
459 if (PROFILE_STARTUP) {
460 android.os.Debug.stopMethodTracing();
461 }
462
463 if (!mRestoring) {
464 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
465 // If the user leaves launcher, then we should just load items asynchronously when
466 // they return.
467 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
468 } else {
469 // We only load the page synchronously if the user rotates (or triggers a
470 // configuration change) while launcher is in the foreground
471 mModel.startLoader(true, mWorkspace.getRestorePage());
472 }
473 }
474
475 // For handling default keys
476 mDefaultKeySsb = new SpannableStringBuilder();
477 Selection.setSelection(mDefaultKeySsb, 0);
478
479 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
480 registerReceiver(mCloseSystemDialogsReceiver, filter);
481
482 // On large interfaces, we want the screen to auto-rotate based on the current orientation
483 unlockScreenOrientation(true);
484
485 if (mLauncherCallbacks != null) {
486 mLauncherCallbacks.onCreate(savedInstanceState);
487 if (mLauncherCallbacks.hasLauncherOverlay()) {
488 ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
489 mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
490 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
491 mLauncherOverlayContainer, mLauncherOverlayCallbacks);
492 mWorkspace.setLauncherOverlay(mLauncherOverlay);
493 }
494 }
495
496 if (shouldShowIntroScreen()) {
497 showIntroScreen();
498 } else {
499 showFirstRunActivity();
500 showFirstRunClings();
501 }
502 }
503
504 private LauncherCallbacks mLauncherCallbacks;
505
506 public void onPostCreate(Bundle savedInstanceState) {
507 super.onPostCreate(savedInstanceState);
508 if (mLauncherCallbacks != null) {
509 mLauncherCallbacks.onPostCreate(savedInstanceState);
510 }
511 }
512
513 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
514 mLauncherCallbacks = callbacks;
515 return true;
516 }
517
518 @Override
519 public void onLauncherProviderChange() {
520 if (mLauncherCallbacks != null) {
521 mLauncherCallbacks.onLauncherProviderChange();
522 }
523 }
524
525 /** To be overridden by subclasses to hint to Launcher that we have custom content */
526 protected boolean hasCustomContentToLeft() {
527 if (mLauncherCallbacks != null) {
528 return mLauncherCallbacks.hasCustomContentToLeft();
529 }
530 return false;
531 }
532
533 /**
534 * To be overridden by subclasses to populate the custom content container and call
535 * {@link #addToCustomContentPage}. This will only be invoked if
536 * {@link #hasCustomContentToLeft()} is {@code true}.
537 */
538 protected void populateCustomContentContainer() {
539 if (mLauncherCallbacks != null) {
540 mLauncherCallbacks.populateCustomContentContainer();
541 }
542 }
543
544 /**
545 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
546 * ensure the custom content page is added or removed if necessary.
547 */
548 protected void invalidateHasCustomContentToLeft() {
549 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
550 // Not bound yet, wait for bindScreens to be called.
551 return;
552 }
553
554 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
555 // Create the custom content page and call the subclass to populate it.
556 mWorkspace.createCustomContentContainer();
557 populateCustomContentContainer();
558 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
559 mWorkspace.removeCustomContentPage();
560 }
561 }
562
563 private void checkForLocaleChange() {
564 if (sLocaleConfiguration == null) {
565 new AsyncTask<Void, Void, LocaleConfiguration>() {
566 @Override
567 protected LocaleConfiguration doInBackground(Void... unused) {
568 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
569 readConfiguration(Launcher.this, localeConfiguration);
570 return localeConfiguration;
571 }
572
573 @Override
574 protected void onPostExecute(LocaleConfiguration result) {
575 sLocaleConfiguration = result;
576 checkForLocaleChange(); // recursive, but now with a locale configuration
577 }
578 }.execute();
579 return;
580 }
581
582 final Configuration configuration = getResources().getConfiguration();
583
584 final String previousLocale = sLocaleConfiguration.locale;
585 final String locale = configuration.locale.toString();
586
587 final int previousMcc = sLocaleConfiguration.mcc;
588 final int mcc = configuration.mcc;
589
590 final int previousMnc = sLocaleConfiguration.mnc;
591 final int mnc = configuration.mnc;
592
593 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
594
595 if (localeChanged) {
596 sLocaleConfiguration.locale = locale;
597 sLocaleConfiguration.mcc = mcc;
598 sLocaleConfiguration.mnc = mnc;
599
600 mIconCache.flush();
601
602 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
603 new AsyncTask<Void, Void, Void>() {
604 public Void doInBackground(Void ... args) {
605 writeConfiguration(Launcher.this, localeConfiguration);
606 return null;
607 }
608 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
609 }
610 }
611
612 private static class LocaleConfiguration {
613 public String locale;
614 public int mcc = -1;
615 public int mnc = -1;
616 }
617
618 private static void readConfiguration(Context context, LocaleConfiguration configuration) {
619 DataInputStream in = null;
620 try {
621 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
622 configuration.locale = in.readUTF();
623 configuration.mcc = in.readInt();
624 configuration.mnc = in.readInt();
625 } catch (FileNotFoundException e) {
626 // Ignore
627 } catch (IOException e) {
628 // Ignore
629 } finally {
630 if (in != null) {
631 try {
632 in.close();
633 } catch (IOException e) {
634 // Ignore
635 }
636 }
637 }
638 }
639
640 private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
641 DataOutputStream out = null;
642 try {
643 out = new DataOutputStream(context.openFileOutput(
644 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
645 out.writeUTF(configuration.locale);
646 out.writeInt(configuration.mcc);
647 out.writeInt(configuration.mnc);
648 out.flush();
649 } catch (FileNotFoundException e) {
650 // Ignore
651 } catch (IOException e) {
652 //noinspection ResultOfMethodCallIgnored
653 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
654 } finally {
655 if (out != null) {
656 try {
657 out.close();
658 } catch (IOException e) {
659 // Ignore
660 }
661 }
662 }
663 }
664
665 public Stats getStats() {
666 return mStats;
667 }
668
669 public LayoutInflater getInflater() {
670 return mInflater;
671 }
672
673 boolean isDraggingEnabled() {
674 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
675 // that is subsequently removed from the workspace in startBinding().
676 return !mModel.isLoadingWorkspace();
677 }
678
679 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
680 public static int generateViewId() {
681 if (Build.VERSION.SDK_INT >= 17) {
682 return View.generateViewId();
683 } else {
684 // View.generateViewId() is not available. The following fallback logic is a copy
685 // of its implementation.
686 for (;;) {
687 final int result = sNextGeneratedId.get();
688 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
689 int newValue = result + 1;
690 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
691 if (sNextGeneratedId.compareAndSet(result, newValue)) {
692 return result;
693 }
694 }
695 }
696 }
697
698 public int getViewIdForItem(ItemInfo info) {
699 // This cast is safe given the > 2B range for int.
700 int itemId = (int) info.id;
701 if (mItemIdToViewId.containsKey(itemId)) {
702 return mItemIdToViewId.get(itemId);
703 }
704 int viewId = generateViewId();
705 mItemIdToViewId.put(itemId, viewId);
706 return viewId;
707 }
708
709 /**
710 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
711 * a configuration step, this allows the proper animations to run after other transitions.
712 */
713 private long completeAdd(PendingAddArguments args) {
714 long screenId = args.screenId;
715 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
716 // When the screen id represents an actual screen (as opposed to a rank) we make sure
717 // that the drop page actually exists.
718 screenId = ensurePendingDropLayoutExists(args.screenId);
719 }
720
721 switch (args.requestCode) {
722 case REQUEST_CREATE_SHORTCUT:
723 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
724 args.cellY);
725 break;
726 case REQUEST_CREATE_APPWIDGET:
727 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
728 break;
729 case REQUEST_RECONFIGURE_APPWIDGET:
730 completeRestoreAppWidget(args.appWidgetId);
731 break;
732 }
733 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
734 // if you turned the screen off and then back while in All Apps, Launcher would not
735 // return to the workspace. Clearing mAddInfo.container here fixes this issue
736 resetAddInfo();
737 return screenId;
738 }
739
740 private void handleActivityResult(
741 final int requestCode, final int resultCode, final Intent data) {
742 // Reset the startActivity waiting flag
743 setWaitingForResult(false);
744 final int pendingAddWidgetId = mPendingAddWidgetId;
745 mPendingAddWidgetId = -1;
746
747 Runnable exitSpringLoaded = new Runnable() {
748 @Override
749 public void run() {
750 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
751 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
752 }
753 };
754
755 if (requestCode == REQUEST_BIND_APPWIDGET) {
756 final int appWidgetId = data != null ?
757 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
758 if (resultCode == RESULT_CANCELED) {
759 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
760 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
761 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
762 } else if (resultCode == RESULT_OK) {
763 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
764 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
765 }
766 return;
767 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
768 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
769 mWorkspace.exitOverviewMode(false);
770 }
771 return;
772 }
773
774 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
775 requestCode == REQUEST_CREATE_APPWIDGET);
776
777 final boolean workspaceLocked = isWorkspaceLocked();
778 // We have special handling for widgets
779 if (isWidgetDrop) {
780 final int appWidgetId;
781 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
782 : -1;
783 if (widgetId < 0) {
784 appWidgetId = pendingAddWidgetId;
785 } else {
786 appWidgetId = widgetId;
787 }
788
789 final int result;
790 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
791 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
792 "returned from the widget configuration activity.");
793 result = RESULT_CANCELED;
794 completeTwoStageWidgetDrop(result, appWidgetId);
795 final Runnable onComplete = new Runnable() {
796 @Override
797 public void run() {
798 exitSpringLoadedDragModeDelayed(false, 0, null);
799 }
800 };
801 if (workspaceLocked) {
802 // No need to remove the empty screen if we're mid-binding, as the
803 // the bind will not add the empty screen.
804 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
805 } else {
806 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
807 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
808 }
809 } else {
810 if (!workspaceLocked) {
811 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
812 // When the screen id represents an actual screen (as opposed to a rank)
813 // we make sure that the drop page actually exists.
814 mPendingAddInfo.screenId =
815 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
816 }
817 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
818
819 dropLayout.setDropPending(true);
820 final Runnable onComplete = new Runnable() {
821 @Override
822 public void run() {
823 completeTwoStageWidgetDrop(resultCode, appWidgetId);
824 dropLayout.setDropPending(false);
825 }
826 };
827 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
828 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
829 } else {
830 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
831 mPendingAddInfo);
832 sPendingAddItem = args;
833 }
834 }
835 return;
836 }
837
838 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
839 if (resultCode == RESULT_OK) {
840 // Update the widget view.
841 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
842 pendingAddWidgetId, mPendingAddInfo);
843 if (workspaceLocked) {
844 sPendingAddItem = args;
845 } else {
846 completeAdd(args);
847 }
848 }
849 // Leave the widget in the pending state if the user canceled the configure.
850 return;
851 }
852
853 // The pattern used here is that a user PICKs a specific application,
854 // which, depending on the target, might need to CREATE the actual target.
855
856 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
857 // launch over to the Music app to actually CREATE_SHORTCUT.
858 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
859 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
860 mPendingAddInfo);
861 if (isWorkspaceLocked()) {
862 sPendingAddItem = args;
863 } else {
864 completeAdd(args);
865 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
866 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
867 }
868 } else if (resultCode == RESULT_CANCELED) {
869 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
870 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
871 }
872 mDragLayer.clearAnimatedView();
873
874 }
875
876 @Override
877 protected void onActivityResult(
878 final int requestCode, final int resultCode, final Intent data) {
879 handleActivityResult(requestCode, resultCode, data);
880 if (mLauncherCallbacks != null) {
881 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
882 }
883 }
884
885 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
886 appWidgetId, ItemInfo info) {
887 PendingAddArguments args = new PendingAddArguments();
888 args.requestCode = requestCode;
889 args.intent = data;
890 args.container = info.container;
891 args.screenId = info.screenId;
892 args.cellX = info.cellX;
893 args.cellY = info.cellY;
894 args.appWidgetId = appWidgetId;
895 return args;
896 }
897
898 /**
899 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
900 *
901 * @param screenId the screen id to check
902 * @return the new screen, or screenId if it exists
903 */
904 private long ensurePendingDropLayoutExists(long screenId) {
905 CellLayout dropLayout =
906 (CellLayout) mWorkspace.getScreenWithId(screenId);
907 if (dropLayout == null) {
908 // it's possible that the add screen was removed because it was
909 // empty and a re-bind occurred
910 mWorkspace.addExtraEmptyScreen();
911 return mWorkspace.commitExtraEmptyScreen();
912 } else {
913 return screenId;
914 }
915 }
916
917 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
918 CellLayout cellLayout =
919 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
920 Runnable onCompleteRunnable = null;
921 int animationType = 0;
922
923 AppWidgetHostView boundWidget = null;
924 if (resultCode == RESULT_OK) {
925 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
926 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
927 mPendingAddWidgetInfo);
928 boundWidget = layout;
929 onCompleteRunnable = new Runnable() {
930 @Override
931 public void run() {
932 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
933 mPendingAddInfo.screenId, layout, null);
934 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
935 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
936 }
937 };
938 } else if (resultCode == RESULT_CANCELED) {
939 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
940 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
941 }
942 if (mDragLayer.getAnimatedView() != null) {
943 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
944 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
945 animationType, boundWidget, true);
946 } else if (onCompleteRunnable != null) {
947 // The animated view may be null in the case of a rotation during widget configuration
948 onCompleteRunnable.run();
949 }
950 }
951
952 @Override
953 protected void onStop() {
954 super.onStop();
955 FirstFrameAnimatorHelper.setIsVisible(false);
956
957 if (mLauncherCallbacks != null) {
958 mLauncherCallbacks.onStop();
959 }
960 }
961
962 @Override
963 protected void onStart() {
964 super.onStart();
965 FirstFrameAnimatorHelper.setIsVisible(true);
966
967 if (mLauncherCallbacks != null) {
968 mLauncherCallbacks.onStart();
969 }
970 }
971
972 @Override
973 protected void onResume() {
974 long startTime = 0;
975 if (DEBUG_RESUME_TIME) {
976 startTime = System.currentTimeMillis();
977 Log.v(TAG, "Launcher.onResume()");
978 }
979
980 if (mLauncherCallbacks != null) {
981 mLauncherCallbacks.preOnResume();
982 }
983
984 super.onResume();
985
986 // Restore the previous launcher state
987 if (mOnResumeState == State.WORKSPACE || mOnResumeState == State.NONE) {
988 showWorkspace(false);
989 } else if (mOnResumeState == State.APPS) {
990 showAppsView(false /* animated */, false /* resetListToTop */);
991 } else if (mOnResumeState == State.WIDGETS) {
992 showWidgetsView(false, false);
993 }
994 mOnResumeState = State.NONE;
995
996 // Background was set to gradient in onPause(), restore to black if in all apps.
997 setWorkspaceBackground(mState == State.WORKSPACE);
998
999 mPaused = false;
1000 if (mRestoring || mOnResumeNeedsLoad) {
1001 setWorkspaceLoading(true);
1002 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1003 mRestoring = false;
1004 mOnResumeNeedsLoad = false;
1005 }
1006 if (mBindOnResumeCallbacks.size() > 0) {
1007 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1008 // execute them here
1009 long startTimeCallbacks = 0;
1010 if (DEBUG_RESUME_TIME) {
1011 startTimeCallbacks = System.currentTimeMillis();
1012 }
1013
1014 if (mAppsCustomizeContent != null) {
1015 mAppsCustomizeContent.setBulkBind(true);
1016 }
1017 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1018 mBindOnResumeCallbacks.get(i).run();
1019 }
1020 if (mAppsCustomizeContent != null) {
1021 mAppsCustomizeContent.setBulkBind(false);
1022 }
1023 mBindOnResumeCallbacks.clear();
1024 if (DEBUG_RESUME_TIME) {
1025 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1026 (System.currentTimeMillis() - startTimeCallbacks));
1027 }
1028 }
1029 if (mOnResumeCallbacks.size() > 0) {
1030 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1031 mOnResumeCallbacks.get(i).run();
1032 }
1033 mOnResumeCallbacks.clear();
1034 }
1035
1036 // Reset the pressed state of icons that were locked in the press state while activities
1037 // were launching
1038 if (mWaitingForResume != null) {
1039 // Resets the previous workspace icon press state
1040 mWaitingForResume.setStayPressed(false);
1041 }
1042
1043 // It is possible that widgets can receive updates while launcher is not in the foreground.
1044 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1045 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1046 // orientation.
1047 getWorkspace().reinflateWidgetsIfNecessary();
1048
1049 // Process any items that were added while Launcher was away.
1050 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1051
1052 if (DEBUG_RESUME_TIME) {
1053 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1054 }
1055
1056 if (mWorkspace.getCustomContentCallbacks() != null) {
1057 // If we are resuming and the custom content is the current page, we call onShow().
1058 // It is also poassible that onShow will instead be called slightly after first layout
1059 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1060 if (mWorkspace.isOnOrMovingToCustomContent()) {
1061 mWorkspace.getCustomContentCallbacks().onShow(true);
1062 }
1063 }
1064 mWorkspace.updateInteractionForState();
1065 mWorkspace.onResume();
1066
1067 PackageInstallerCompat.getInstance(this).onResume();
1068
1069 if (mLauncherCallbacks != null) {
1070 mLauncherCallbacks.onResume();
1071 }
1072 }
1073
1074 @Override
1075 protected void onPause() {
1076 // Ensure that items added to Launcher are queued until Launcher returns
1077 InstallShortcutReceiver.enableInstallQueue();
1078 PackageInstallerCompat.getInstance(this).onPause();
1079
1080 super.onPause();
1081 mPaused = true;
1082 mDragController.cancelDrag();
1083 mDragController.resetLastGestureUpTime();
1084
1085 // We call onHide() aggressively. The custom content callbacks should be able to
1086 // debounce excess onHide calls.
1087 if (mWorkspace.getCustomContentCallbacks() != null) {
1088 mWorkspace.getCustomContentCallbacks().onHide();
1089 }
1090
1091 if (mLauncherCallbacks != null) {
1092 mLauncherCallbacks.onPause();
1093 }
1094 }
1095
1096 public interface CustomContentCallbacks {
1097 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1098 // by a onResume or by scrolling otherwise.
1099 public void onShow(boolean fromResume);
1100
1101 // Custom content is completely hidden
1102 public void onHide();
1103
1104 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1105 public void onScrollProgressChanged(float progress);
1106
1107 // Indicates whether the user is allowed to scroll away from the custom content.
1108 boolean isScrollingAllowed();
1109 }
1110
1111 public interface LauncherOverlay {
1112
1113 /**
1114 * Touch interaction leading to overscroll has begun
1115 */
1116 public void onScrollInteractionBegin();
1117
1118 /**
1119 * Touch interaction related to overscroll has ended
1120 */
1121 public void onScrollInteractionEnd();
1122
1123 /**
1124 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1125 * screen (or in the case of RTL, the rightmost screen).
1126 */
1127 public void onScrollChange(int progress, boolean rtl);
1128
1129 /**
1130 * Screen has stopped scrolling
1131 */
1132 public void onScrollSettled();
1133
1134 /**
1135 * This method can be called by the Launcher in order to force the LauncherOverlay
1136 * to exit fully immersive mode.
1137 */
1138 public void forceExitFullImmersion();
1139 }
1140
1141 public interface LauncherOverlayCallbacks {
1142 /**
1143 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1144 * however it doesn't modify any state within the launcher.
1145 */
1146 public boolean canEnterFullImmersion();
1147
1148 /**
1149 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1150 * eg. by occupying the full screen and handling all touch events.
1151 *
1152 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1153 * case, Launcher will modify any necessary state and assumes the overlay is
1154 * handling all interaction. If false, the LauncherOverlay should cancel any
1155 *
1156 */
1157 public boolean enterFullImmersion();
1158
1159 /**
1160 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1161 * full control over UI and state.
1162 */
1163 public void exitFullImmersion();
1164 }
1165
1166 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1167
1168 @Override
1169 public boolean canEnterFullImmersion() {
1170 return mState == State.WORKSPACE;
1171 }
1172
1173 @Override
1174 public boolean enterFullImmersion() {
1175 if (mState == State.WORKSPACE) {
1176 // When fully immersed, disregard any touches which fall through.
1177 mDragLayer.setBlockTouch(true);
1178 return true;
1179 }
1180 return false;
1181 }
1182
1183 @Override
1184 public void exitFullImmersion() {
1185 mDragLayer.setBlockTouch(false);
1186 }
1187 }
1188
1189 protected boolean hasSettings() {
1190 if (mLauncherCallbacks != null) {
1191 return mLauncherCallbacks.hasSettings();
1192 }
1193 return false;
1194 }
1195
1196
1197 public void addToCustomContentPage(View customContent,
1198 CustomContentCallbacks callbacks, String description) {
1199 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1200 }
1201
1202 // The custom content needs to offset its content to account for the QSB
1203 public int getTopOffsetForCustomContent() {
1204 return mWorkspace.getPaddingTop();
1205 }
1206
1207 @Override
1208 public Object onRetainNonConfigurationInstance() {
1209 // Flag the loader to stop early before switching
1210 if (mModel.isCurrentCallbacks(this)) {
1211 mModel.stopLoader();
1212 }
1213 if (mAppsCustomizeContent != null) {
1214 mAppsCustomizeContent.surrender();
1215 }
1216 return Boolean.TRUE;
1217 }
1218
1219 // We can't hide the IME if it was forced open. So don't bother
1220 @Override
1221 public void onWindowFocusChanged(boolean hasFocus) {
1222 super.onWindowFocusChanged(hasFocus);
1223 mHasFocus = hasFocus;
1224
1225 if (mLauncherCallbacks != null) {
1226 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1227 }
1228 }
1229
1230 private boolean acceptFilter() {
1231 final InputMethodManager inputManager = (InputMethodManager)
1232 getSystemService(Context.INPUT_METHOD_SERVICE);
1233 return !inputManager.isFullscreenMode();
1234 }
1235
1236 @Override
1237 public boolean onKeyDown(int keyCode, KeyEvent event) {
1238 final int uniChar = event.getUnicodeChar();
1239 final boolean handled = super.onKeyDown(keyCode, event);
1240 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1241 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1242 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1243 keyCode, event);
1244 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1245 // something usable has been typed - start a search
1246 // the typed text will be retrieved and cleared by
1247 // showSearchDialog()
1248 // If there are multiple keystrokes before the search dialog takes focus,
1249 // onSearchRequested() will be called for every keystroke,
1250 // but it is idempotent, so it's fine.
1251 return onSearchRequested();
1252 }
1253 }
1254
1255 // Eat the long press event so the keyboard doesn't come up.
1256 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1257 return true;
1258 }
1259
1260 return handled;
1261 }
1262
1263 private String getTypedText() {
1264 return mDefaultKeySsb.toString();
1265 }
1266
1267 private void clearTypedText() {
1268 mDefaultKeySsb.clear();
1269 mDefaultKeySsb.clearSpans();
1270 Selection.setSelection(mDefaultKeySsb, 0);
1271 }
1272
1273 /**
1274 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1275 * State
1276 */
1277 private static State intToState(int stateOrdinal) {
1278 State state = State.WORKSPACE;
1279 final State[] stateValues = State.values();
1280 for (int i = 0; i < stateValues.length; i++) {
1281 if (stateValues[i].ordinal() == stateOrdinal) {
1282 state = stateValues[i];
1283 break;
1284 }
1285 }
1286 return state;
1287 }
1288
1289 /**
1290 * Restores the previous state, if it exists.
1291 *
1292 * @param savedState The previous state.
1293 */
1294 @SuppressWarnings("unchecked")
1295 private void restoreState(Bundle savedState) {
1296 if (savedState == null) {
1297 return;
1298 }
1299
1300 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1301 if (state == State.APPS || state == State.WIDGETS) {
1302 mOnResumeState = state;
1303 }
1304
1305 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1306 PagedView.INVALID_RESTORE_PAGE);
1307 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1308 mWorkspace.setRestorePage(currentScreen);
1309 }
1310
1311 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1312 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1313
1314 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1315 mPendingAddInfo.container = pendingAddContainer;
1316 mPendingAddInfo.screenId = pendingAddScreen;
1317 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1318 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1319 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1320 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1321 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1322 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1323 setWaitingForResult(true);
1324 mRestoring = true;
1325 }
1326
1327 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1328 if (renameFolder) {
1329 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1330 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1331 mRestoring = true;
1332 }
1333
1334 // Restore the AppsCustomize tab
1335 if (mAppsCustomizeTabHost != null) {
1336 String curTab = savedState.getString("apps_customize_currentTab");
1337 if (curTab != null) {
1338 mAppsCustomizeTabHost.setContentTypeImmediate(
1339 mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
1340 mAppsCustomizeContent.loadAssociatedPages(
1341 mAppsCustomizeContent.getCurrentPage());
1342 }
1343
1344 int currentIndex = savedState.getInt("apps_customize_currentIndex");
1345 mAppsCustomizeContent.restorePageForIndex(currentIndex);
1346 }
1347 mItemIdToViewId = (HashMap<Integer, Integer>)
1348 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1349 }
1350
1351 /**
1352 * Finds all the views we need and configure them properly.
1353 */
1354 private void setupViews() {
1355 final DragController dragController = mDragController;
1356
1357 mLauncherView = findViewById(R.id.launcher);
1358 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1359 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1360 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1361 mWorkspace.setPageSwitchListener(this);
1362 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1363
1364 mLauncherView.setSystemUiVisibility(
1365 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1366 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1367
1368 // Setup the drag layer
1369 mDragLayer.setup(this, dragController);
1370
1371 // Setup the hotseat
1372 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1373 if (mHotseat != null) {
1374 mHotseat.setup(this);
1375 mHotseat.setOnLongClickListener(this);
1376 }
1377
1378 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1379 View widgetButton = findViewById(R.id.widget_button);
1380 widgetButton.setOnClickListener(new OnClickListener() {
1381 @Override
1382 public void onClick(View arg0) {
1383 if (!mWorkspace.isSwitchingState()) {
1384 onClickAddWidgetButton(arg0);
1385 }
1386 }
1387 });
1388 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1389
1390 View wallpaperButton = findViewById(R.id.wallpaper_button);
1391 wallpaperButton.setOnClickListener(new OnClickListener() {
1392 @Override
1393 public void onClick(View arg0) {
1394 if (!mWorkspace.isSwitchingState()) {
1395 onClickWallpaperPicker(arg0);
1396 }
1397 }
1398 });
1399 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1400
1401 View settingsButton = findViewById(R.id.settings_button);
1402 if (hasSettings()) {
1403 settingsButton.setOnClickListener(new OnClickListener() {
1404 @Override
1405 public void onClick(View arg0) {
1406 if (!mWorkspace.isSwitchingState()) {
1407 onClickSettingsButton(arg0);
1408 }
1409 }
1410 });
1411 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1412 } else {
1413 settingsButton.setVisibility(View.GONE);
1414 }
1415
1416 mOverviewPanel.setAlpha(0f);
1417
1418 // Setup the workspace
1419 mWorkspace.setHapticFeedbackEnabled(false);
1420 mWorkspace.setOnLongClickListener(this);
1421 mWorkspace.setup(dragController);
1422 dragController.addDragListener(mWorkspace);
1423
1424 // Get the search/delete bar
1425 mSearchDropTargetBar = (SearchDropTargetBar)
1426 mDragLayer.findViewById(R.id.search_drop_target_bar);
1427
1428 // Setup Apps
1429 mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
1430
1431 // Setup AppsCustomize
1432 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
1433 mAppsCustomizeContent = (AppsCustomizePagedView)
1434 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
1435 mAppsCustomizeContent.setup(this, dragController);
1436
1437 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1438 dragController.setDragScoller(mWorkspace);
1439 dragController.setScrollView(mDragLayer);
1440 dragController.setMoveTarget(mWorkspace);
1441 dragController.addDropTarget(mWorkspace);
1442 if (mSearchDropTargetBar != null) {
1443 mSearchDropTargetBar.setup(this, dragController);
1444 if (getOrCreateQsbBar() == null) {
1445 // Explicitly set it to null during initialization.
1446 mSearchDropTargetBar.setQsbSearchBar(null);
1447 }
1448 }
1449
1450 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1451 Log.v(TAG, "adding WeightWatcher");
1452 mWeightWatcher = new WeightWatcher(this);
1453 mWeightWatcher.setAlpha(0.5f);
1454 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1455 new FrameLayout.LayoutParams(
1456 FrameLayout.LayoutParams.MATCH_PARENT,
1457 FrameLayout.LayoutParams.WRAP_CONTENT,
1458 Gravity.BOTTOM)
1459 );
1460
1461 boolean show = shouldShowWeightWatcher();
1462 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1463 }
1464 }
1465
1466 /**
1467 * Sets the all apps button. This method is called from {@link Hotseat}.
1468 */
1469 public void setAllAppsButton(View allAppsButton) {
1470 mAllAppsButton = allAppsButton;
1471 }
1472
1473 public View getAllAppsButton() {
1474 return mAllAppsButton;
1475 }
1476
1477 /**
1478 * Creates a view representing a shortcut.
1479 *
1480 * @param info The data structure describing the shortcut.
1481 *
1482 * @return A View inflated from R.layout.application.
1483 */
1484 View createShortcut(ShortcutInfo info) {
1485 return createShortcut(R.layout.application,
1486 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1487 }
1488
1489 /**
1490 * Creates a view representing a shortcut inflated from the specified resource.
1491 *
1492 * @param layoutResId The id of the XML layout used to create the shortcut.
1493 * @param parent The group the shortcut belongs to.
1494 * @param info The data structure describing the shortcut.
1495 *
1496 * @return A View inflated from layoutResId.
1497 */
1498 public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1499 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1500 favorite.applyFromShortcutInfo(info, mIconCache, true);
1501 favorite.setOnClickListener(this);
1502 favorite.setOnFocusChangeListener(mFocusHandler);
1503 return favorite;
1504 }
1505
1506 /**
1507 * Add a shortcut to the workspace.
1508 *
1509 * @param data The intent describing the shortcut.
1510 * @param cellInfo The position on screen where to create the shortcut.
1511 */
1512 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1513 int cellY) {
1514 int[] cellXY = mTmpAddItemCellCoordinates;
1515 int[] touchXY = mPendingAddInfo.dropPos;
1516 CellLayout layout = getCellLayout(container, screenId);
1517
1518 boolean foundCellSpan = false;
1519
1520 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
1521 if (info == null) {
1522 return;
1523 }
1524 final View view = createShortcut(info);
1525
1526 // First we check if we already know the exact location where we want to add this item.
1527 if (cellX >= 0 && cellY >= 0) {
1528 cellXY[0] = cellX;
1529 cellXY[1] = cellY;
1530 foundCellSpan = true;
1531
1532 // If appropriate, either create a folder or add to an existing folder
1533 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1534 true, null,null)) {
1535 return;
1536 }
1537 DragObject dragObject = new DragObject();
1538 dragObject.dragInfo = info;
1539 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1540 true)) {
1541 return;
1542 }
1543 } else if (touchXY != null) {
1544 // when dragging and dropping, just find the closest free spot
1545 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1546 foundCellSpan = (result != null);
1547 } else {
1548 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1549 }
1550
1551 if (!foundCellSpan) {
1552 showOutOfSpaceMessage(isHotseatLayout(layout));
1553 return;
1554 }
1555
1556 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
1557
1558 if (!mRestoring) {
1559 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1560 isWorkspaceLocked());
1561 }
1562 }
1563
1564 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1565 int minHeight) {
1566 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1567 // We want to account for the extra amount of padding that we are adding to the widget
1568 // to ensure that it gets the full amount of space that it has requested
1569 int requiredWidth = minWidth + padding.left + padding.right;
1570 int requiredHeight = minHeight + padding.top + padding.bottom;
1571 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1572 }
1573
1574 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1575 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1576 }
1577
1578 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1579 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1580 }
1581
1582 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1583 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1584 }
1585
1586 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1587 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1588 info.minResizeHeight);
1589 }
1590
1591 /**
1592 * Add a widget to the workspace.
1593 *
1594 * @param appWidgetId The app widget id
1595 */
1596 private void completeAddAppWidget(int appWidgetId, long container, long screenId,
1597 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1598
1599 ItemInfo info = mPendingAddInfo;
1600 if (appWidgetInfo == null) {
1601 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1602 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1603 }
1604
1605 if (appWidgetInfo.isCustomWidget) {
1606 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1607 }
1608
1609 LauncherAppWidgetInfo launcherInfo;
1610 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1611 launcherInfo.spanX = info.spanX;
1612 launcherInfo.spanY = info.spanY;
1613 launcherInfo.minSpanX = info.minSpanX;
1614 launcherInfo.minSpanY = info.minSpanY;
1615 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1616
1617 LauncherModel.addItemToDatabase(this, launcherInfo,
1618 container, screenId, info.cellX, info.cellY, false);
1619
1620 if (!mRestoring) {
1621 if (hostView == null) {
1622 // Perform actual inflation because we're live
1623 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1624 appWidgetInfo);
1625 } else {
1626 // The AppWidgetHostView has already been inflated and instantiated
1627 launcherInfo.hostView = hostView;
1628 }
1629 launcherInfo.hostView.setTag(launcherInfo);
1630 launcherInfo.hostView.setVisibility(View.VISIBLE);
1631 launcherInfo.notifyWidgetSizeChanged(this);
1632
1633 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1634 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1635
1636 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1637 }
1638 resetAddInfo();
1639 }
1640
1641 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1642 @Override
1643 public void onReceive(Context context, Intent intent) {
1644 final String action = intent.getAction();
1645 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1646 mUserPresent = false;
1647 mDragLayer.clearAllResizeFrames();
1648 updateAutoAdvanceState();
1649
1650 // Reset AllApps to its initial state only if we are not in the middle of
1651 // processing a multi-step drop
1652 if (mAppsView != null && mAppsCustomizeTabHost != null &&
1653 mPendingAddInfo.container == ItemInfo.NO_ID) {
1654 showWorkspace(false);
1655 }
1656 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1657 mUserPresent = true;
1658 updateAutoAdvanceState();
1659 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1660 mModel.resetLoadedState(false, true);
1661 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1662 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1663 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1664 mModel.resetLoadedState(false, true);
1665 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1666 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1667 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1668 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1669 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1670 getModel().forceReload();
1671 }
1672 }
1673 };
1674
1675 @Override
1676 public void onAttachedToWindow() {
1677 super.onAttachedToWindow();
1678
1679 // Listen for broadcasts related to user-presence
1680 final IntentFilter filter = new IntentFilter();
1681 filter.addAction(Intent.ACTION_SCREEN_OFF);
1682 filter.addAction(Intent.ACTION_USER_PRESENT);
1683 // For handling managed profiles
1684 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1685 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1686 if (ENABLE_DEBUG_INTENTS) {
1687 filter.addAction(DebugIntents.DELETE_DATABASE);
1688 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1689 }
1690 registerReceiver(mReceiver, filter);
1691 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1692 setupTransparentSystemBarsForLmp();
1693 mAttached = true;
1694 mVisible = true;
1695 }
1696
1697 /**
1698 * Sets up transparent navigation and status bars in LMP.
1699 * This method is a no-op for other platform versions.
1700 */
1701 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1702 private void setupTransparentSystemBarsForLmp() {
1703 if (Utilities.isLmpOrAbove()) {
1704 Window window = getWindow();
1705 window.getAttributes().systemUiVisibility |=
1706 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1707 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1708 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1709 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1710 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1711 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1712 window.setStatusBarColor(Color.TRANSPARENT);
1713 window.setNavigationBarColor(Color.TRANSPARENT);
1714 }
1715 }
1716
1717 @Override
1718 public void onDetachedFromWindow() {
1719 super.onDetachedFromWindow();
1720 mVisible = false;
1721
1722 if (mAttached) {
1723 unregisterReceiver(mReceiver);
1724 mAttached = false;
1725 }
1726 updateAutoAdvanceState();
1727 }
1728
1729 public void onWindowVisibilityChanged(int visibility) {
1730 mVisible = visibility == View.VISIBLE;
1731 updateAutoAdvanceState();
1732 // The following code used to be in onResume, but it turns out onResume is called when
1733 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1734 // is a more appropriate event to handle
1735 if (mVisible) {
1736 mAppsCustomizeTabHost.onWindowVisible();
1737 if (!mWorkspaceLoading) {
1738 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1739 // We want to let Launcher draw itself at least once before we force it to build
1740 // layers on all the workspace pages, so that transitioning to Launcher from other
1741 // apps is nice and speedy.
1742 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1743 private boolean mStarted = false;
1744 public void onDraw() {
1745 if (mStarted) return;
1746 mStarted = true;
1747 // We delay the layer building a bit in order to give
1748 // other message processing a time to run. In particular
1749 // this avoids a delay in hiding the IME if it was
1750 // currently shown, because doing that may involve
1751 // some communication back with the app.
1752 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1753 final ViewTreeObserver.OnDrawListener listener = this;
1754 mWorkspace.post(new Runnable() {
1755 public void run() {
1756 if (mWorkspace != null &&
1757 mWorkspace.getViewTreeObserver() != null) {
1758 mWorkspace.getViewTreeObserver().
1759 removeOnDrawListener(listener);
1760 }
1761 }
1762 });
1763 return;
1764 }
1765 });
1766 }
1767 clearTypedText();
1768 }
1769 }
1770
1771 private void sendAdvanceMessage(long delay) {
1772 mHandler.removeMessages(ADVANCE_MSG);
1773 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1774 mHandler.sendMessageDelayed(msg, delay);
1775 mAutoAdvanceSentTime = System.currentTimeMillis();
1776 }
1777
1778 private void updateAutoAdvanceState() {
1779 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1780 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1781 mAutoAdvanceRunning = autoAdvanceRunning;
1782 if (autoAdvanceRunning) {
1783 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1784 sendAdvanceMessage(delay);
1785 } else {
1786 if (!mWidgetsToAdvance.isEmpty()) {
1787 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1788 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1789 }
1790 mHandler.removeMessages(ADVANCE_MSG);
1791 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1792 }
1793 }
1794 }
1795
1796 private final Handler mHandler = new Handler() {
1797 @Override
1798 public void handleMessage(Message msg) {
1799 if (msg.what == ADVANCE_MSG) {
1800 int i = 0;
1801 for (View key: mWidgetsToAdvance.keySet()) {
1802 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1803 final int delay = mAdvanceStagger * i;
1804 if (v instanceof Advanceable) {
1805 postDelayed(new Runnable() {
1806 public void run() {
1807 ((Advanceable) v).advance();
1808 }
1809 }, delay);
1810 }
1811 i++;
1812 }
1813 sendAdvanceMessage(mAdvanceInterval);
1814 }
1815 }
1816 };
1817
1818 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1819 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1820 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1821 if (v instanceof Advanceable) {
1822 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1823 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1824 updateAutoAdvanceState();
1825 }
1826 }
1827
1828 void removeWidgetToAutoAdvance(View hostView) {
1829 if (mWidgetsToAdvance.containsKey(hostView)) {
1830 mWidgetsToAdvance.remove(hostView);
1831 updateAutoAdvanceState();
1832 }
1833 }
1834
1835 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1836 removeWidgetToAutoAdvance(launcherInfo.hostView);
1837 launcherInfo.hostView = null;
1838 }
1839
1840 void showOutOfSpaceMessage(boolean isHotseatLayout) {
1841 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1842 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1843 }
1844
1845 public DragLayer getDragLayer() {
1846 return mDragLayer;
1847 }
1848
1849 public AppsContainerView getAppsView() {
1850 return mAppsView;
1851 }
1852
1853 public AppsCustomizeTabHost getWidgetsView() {
1854 return mAppsCustomizeTabHost;
1855 }
1856
1857 public Workspace getWorkspace() {
1858 return mWorkspace;
1859 }
1860
1861 public Hotseat getHotseat() {
1862 return mHotseat;
1863 }
1864
1865 public ViewGroup getOverviewPanel() {
1866 return mOverviewPanel;
1867 }
1868
1869 public SearchDropTargetBar getSearchBar() {
1870 return mSearchDropTargetBar;
1871 }
1872
1873 public LauncherAppWidgetHost getAppWidgetHost() {
1874 return mAppWidgetHost;
1875 }
1876
1877 public LauncherModel getModel() {
1878 return mModel;
1879 }
1880
1881 protected SharedPreferences getSharedPrefs() {
1882 return mSharedPrefs;
1883 }
1884
1885 public void closeSystemDialogs() {
1886 getWindow().closeAllPanels();
1887
1888 // Whatever we were doing is hereby canceled.
1889 setWaitingForResult(false);
1890 }
1891
1892 @Override
1893 protected void onNewIntent(Intent intent) {
1894 long startTime = 0;
1895 if (DEBUG_RESUME_TIME) {
1896 startTime = System.currentTimeMillis();
1897 }
1898 super.onNewIntent(intent);
1899
1900 // Close the menu
1901 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1902 // also will cancel mWaitingForResult.
1903 closeSystemDialogs();
1904
1905 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1906 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1907 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1908
1909 if (mWorkspace == null) {
1910 // Can be cases where mWorkspace is null, this prevents a NPE
1911 return;
1912 }
1913 Folder openFolder = mWorkspace.getOpenFolder();
1914 // In all these cases, only animate if we're already on home
1915 mWorkspace.exitWidgetResizeMode();
1916
1917 boolean moveToDefaultScreen = mLauncherCallbacks != null ?
1918 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1919 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1920 openFolder == null && moveToDefaultScreen) {
1921 mWorkspace.moveToDefaultScreen(true);
1922 }
1923
1924 closeFolder();
1925 exitSpringLoadedDragMode();
1926
1927 // If we are already on home, then just animate back to the workspace,
1928 // otherwise, just wait until onResume to set the state back to Workspace
1929 if (alreadyOnHome) {
1930 showWorkspace(true);
1931 } else {
1932 mOnResumeState = State.WORKSPACE;
1933 }
1934
1935 final View v = getWindow().peekDecorView();
1936 if (v != null && v.getWindowToken() != null) {
1937 InputMethodManager imm = (InputMethodManager)getSystemService(
1938 INPUT_METHOD_SERVICE);
1939 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1940 }
1941
1942 // Reset the apps view
1943 if (!alreadyOnHome && mAppsView != null) {
1944 mAppsView.scrollToTop();
1945 }
1946
1947 // Reset the apps customize page
1948 if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
1949 mAppsCustomizeTabHost.reset();
1950 }
1951
1952 if (mLauncherCallbacks != null) {
1953 mLauncherCallbacks.onHomeIntent();
1954 }
1955 }
1956
1957 if (DEBUG_RESUME_TIME) {
1958 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1959 }
1960
1961 if (mLauncherCallbacks != null) {
1962 mLauncherCallbacks.onNewIntent(intent);
1963 }
1964 }
1965
1966 @Override
1967 public void onRestoreInstanceState(Bundle state) {
1968 super.onRestoreInstanceState(state);
1969 for (int page: mSynchronouslyBoundPages) {
1970 mWorkspace.restoreInstanceStateForChild(page);
1971 }
1972 }
1973
1974 @Override
1975 protected void onSaveInstanceState(Bundle outState) {
1976 if (mWorkspace.getChildCount() > 0) {
1977 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1978 mWorkspace.getCurrentPageOffsetFromCustomContent());
1979 }
1980 super.onSaveInstanceState(outState);
1981
1982 outState.putInt(RUNTIME_STATE, mState.ordinal());
1983 // We close any open folder since it will not be re-opened, and we need to make sure
1984 // this state is reflected.
1985 closeFolder();
1986
1987 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
1988 mWaitingForResult) {
1989 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1990 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1991 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1992 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1993 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1994 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1995 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1996 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1997 }
1998
1999 if (mFolderInfo != null && mWaitingForResult) {
2000 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
2001 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
2002 }
2003
2004 // Save the current AppsCustomize tab
2005 if (mAppsCustomizeTabHost != null) {
2006 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
2007 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
2008 if (currentTabTag != null) {
2009 outState.putString("apps_customize_currentTab", currentTabTag);
2010 }
2011 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
2012 outState.putInt("apps_customize_currentIndex", currentIndex);
2013 }
2014 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2015
2016 if (mLauncherCallbacks != null) {
2017 mLauncherCallbacks.onSaveInstanceState(outState);
2018 }
2019 }
2020
2021 @Override
2022 public void onDestroy() {
2023 super.onDestroy();
2024
2025 // Remove all pending runnables
2026 mHandler.removeMessages(ADVANCE_MSG);
2027 mHandler.removeMessages(0);
2028 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2029
2030 // Stop callbacks from LauncherModel
2031 LauncherAppState app = (LauncherAppState.getInstance());
2032
2033 // It's possible to receive onDestroy after a new Launcher activity has
2034 // been created. In this case, don't interfere with the new Launcher.
2035 if (mModel.isCurrentCallbacks(this)) {
2036 mModel.stopLoader();
2037 app.setLauncher(null);
2038 }
2039
2040 try {
2041 mAppWidgetHost.stopListening();
2042 } catch (NullPointerException ex) {
2043 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2044 }
2045 mAppWidgetHost = null;
2046
2047 mWidgetsToAdvance.clear();
2048
2049 TextKeyListener.getInstance().release();
2050
2051 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2052 // to prevent leaking Launcher activities on orientation change.
2053 if (mModel != null) {
2054 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2055 }
2056
2057 getContentResolver().unregisterContentObserver(mWidgetObserver);
2058 unregisterReceiver(mCloseSystemDialogsReceiver);
2059
2060 mDragLayer.clearAllResizeFrames();
2061 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2062 mWorkspace.removeAllWorkspaceScreens();
2063 mWorkspace = null;
2064 mDragController = null;
2065
2066 LauncherAnimUtils.onDestroyActivity();
2067
2068 if (mLauncherCallbacks != null) {
2069 mLauncherCallbacks.onDestroy();
2070 }
2071 }
2072
2073 public DragController getDragController() {
2074 return mDragController;
2075 }
2076
2077 @Override
2078 public void startActivityForResult(Intent intent, int requestCode) {
2079 if (requestCode >= 0) {
2080 setWaitingForResult(true);
2081 }
2082 super.startActivityForResult(intent, requestCode);
2083 }
2084
2085 /**
2086 * Indicates that we want global search for this activity by setting the globalSearch
2087 * argument for {@link #startSearch} to true.
2088 */
2089 @Override
2090 public void startSearch(String initialQuery, boolean selectInitialQuery,
2091 Bundle appSearchData, boolean globalSearch) {
2092
2093 showWorkspace(true);
2094
2095 if (initialQuery == null) {
2096 // Use any text typed in the launcher as the initial query
2097 initialQuery = getTypedText();
2098 }
2099 if (appSearchData == null) {
2100 appSearchData = new Bundle();
2101 appSearchData.putString("source", "launcher-search");
2102 }
2103 Rect sourceBounds = new Rect();
2104 if (mSearchDropTargetBar != null) {
2105 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2106 }
2107
2108 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2109 appSearchData, sourceBounds);
2110 if (clearTextImmediately) {
2111 clearTypedText();
2112 }
2113 }
2114
2115 /**
2116 * Start a text search.
2117 *
2118 * @return {@code true} if the search will start immediately, so any further keypresses
2119 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2120 * to buffer keypresses.
2121 */
2122 public boolean startSearch(String initialQuery,
2123 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2124 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2125 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2126 sourceBounds);
2127 }
2128
2129 startGlobalSearch(initialQuery, selectInitialQuery,
2130 appSearchData, sourceBounds);
2131 return false;
2132 }
2133
2134 /**
2135 * Starts the global search activity. This code is a copied from SearchManager
2136 */
2137 private void startGlobalSearch(String initialQuery,
2138 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2139 final SearchManager searchManager =
2140 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2141 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2142 if (globalSearchActivity == null) {
2143 Log.w(TAG, "No global search activity found.");
2144 return;
2145 }
2146 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2147 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2148 intent.setComponent(globalSearchActivity);
2149 // Make sure that we have a Bundle to put source in
2150 if (appSearchData == null) {
2151 appSearchData = new Bundle();
2152 } else {
2153 appSearchData = new Bundle(appSearchData);
2154 }
2155 // Set source to package name of app that starts global search if not set already.
2156 if (!appSearchData.containsKey("source")) {
2157 appSearchData.putString("source", getPackageName());
2158 }
2159 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2160 if (!TextUtils.isEmpty(initialQuery)) {
2161 intent.putExtra(SearchManager.QUERY, initialQuery);
2162 }
2163 if (selectInitialQuery) {
2164 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2165 }
2166 intent.setSourceBounds(sourceBounds);
2167 try {
2168 startActivity(intent);
2169 } catch (ActivityNotFoundException ex) {
2170 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2171 }
2172 }
2173
2174 public boolean isOnCustomContent() {
2175 return mWorkspace.isOnOrMovingToCustomContent();
2176 }
2177
2178 @Override
2179 public boolean onPrepareOptionsMenu(Menu menu) {
2180 super.onPrepareOptionsMenu(menu);
2181 if (!isOnCustomContent()) {
2182 // Close any open folders
2183 closeFolder();
2184 // Stop resizing any widgets
2185 mWorkspace.exitWidgetResizeMode();
2186 if (!mWorkspace.isInOverviewMode()) {
2187 // Show the overview mode
2188 showOverviewMode(true);
2189 } else {
2190 showWorkspace(true);
2191 }
2192 }
2193 if (mLauncherCallbacks != null) {
2194 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2195 }
2196
2197 return false;
2198 }
2199
2200 @Override
2201 public boolean onSearchRequested() {
2202 startSearch(null, false, null, true);
2203 // Use a custom animation for launching search
2204 return true;
2205 }
2206
2207 public boolean isWorkspaceLocked() {
2208 return mWorkspaceLoading || mWaitingForResult;
2209 }
2210
2211 public boolean isWorkspaceLoading() {
2212 return mWorkspaceLoading;
2213 }
2214
2215 private void setWorkspaceLoading(boolean value) {
2216 boolean isLocked = isWorkspaceLocked();
2217 mWorkspaceLoading = value;
2218 if (isLocked != isWorkspaceLocked()) {
2219 onWorkspaceLockedChanged();
2220 }
2221 }
2222
2223 private void setWaitingForResult(boolean value) {
2224 boolean isLocked = isWorkspaceLocked();
2225 mWaitingForResult = value;
2226 if (isLocked != isWorkspaceLocked()) {
2227 onWorkspaceLockedChanged();
2228 }
2229 }
2230
2231 protected void onWorkspaceLockedChanged() {
2232 if (mLauncherCallbacks != null) {
2233 mLauncherCallbacks.onWorkspaceLockedChanged();
2234 }
2235 }
2236
2237 private void resetAddInfo() {
2238 mPendingAddInfo.container = ItemInfo.NO_ID;
2239 mPendingAddInfo.screenId = -1;
2240 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2241 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2242 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2243 mPendingAddInfo.dropPos = null;
2244 }
2245
2246 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2247 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2248 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2249 }
2250
2251 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2252 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2253 int delay) {
2254 if (appWidgetInfo.configure != null) {
2255 mPendingAddWidgetInfo = appWidgetInfo;
2256 mPendingAddWidgetId = appWidgetId;
2257
2258 // Launch over to configure widget, if needed
2259 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2260 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2261
2262 } else {
2263 // Otherwise just add it
2264 Runnable onComplete = new Runnable() {
2265 @Override
2266 public void run() {
2267 // Exit spring loaded mode if necessary after adding the widget
2268 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2269 null);
2270 }
2271 };
2272 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2273 appWidgetInfo);
2274 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2275 }
2276 }
2277
2278 protected void moveToCustomContentScreen(boolean animate) {
2279 // Close any folders that may be open.
2280 closeFolder();
2281 mWorkspace.moveToCustomContentScreen(animate);
2282 }
2283
2284 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2285 int[] cell, int spanX, int spanY) {
2286 switch (info.itemType) {
2287 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2288 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2289 int span[] = new int[2];
2290 span[0] = spanX;
2291 span[1] = spanY;
2292 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2293 container, screenId, cell, span);
2294 break;
2295 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2296 processShortcutFromDrop(info.componentName, container, screenId, cell);
2297 break;
2298 default:
2299 throw new IllegalStateException("Unknown item type: " + info.itemType);
2300 }
2301 }
2302
2303 /**
2304 * Process a shortcut drop.
2305 *
2306 * @param componentName The name of the component
2307 * @param screenId The ID of the screen where it should be added
2308 * @param cell The cell it should be added to, optional
2309 */
2310 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2311 int[] cell) {
2312 resetAddInfo();
2313 mPendingAddInfo.container = container;
2314 mPendingAddInfo.screenId = screenId;
2315 mPendingAddInfo.dropPos = null;
2316
2317 if (cell != null) {
2318 mPendingAddInfo.cellX = cell[0];
2319 mPendingAddInfo.cellY = cell[1];
2320 }
2321
2322 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2323 createShortcutIntent.setComponent(componentName);
2324 processShortcut(createShortcutIntent);
2325 }
2326
2327 /**
2328 * Process a widget drop.
2329 *
2330 * @param info The PendingAppWidgetInfo of the widget being added.
2331 * @param screenId The ID of the screen where it should be added
2332 * @param cell The cell it should be added to, optional
2333 */
2334 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2335 int[] cell, int[] span) {
2336 resetAddInfo();
2337 mPendingAddInfo.container = info.container = container;
2338 mPendingAddInfo.screenId = info.screenId = screenId;
2339 mPendingAddInfo.dropPos = null;
2340 mPendingAddInfo.minSpanX = info.minSpanX;
2341 mPendingAddInfo.minSpanY = info.minSpanY;
2342
2343 if (cell != null) {
2344 mPendingAddInfo.cellX = cell[0];
2345 mPendingAddInfo.cellY = cell[1];
2346 }
2347 if (span != null) {
2348 mPendingAddInfo.spanX = span[0];
2349 mPendingAddInfo.spanY = span[1];
2350 }
2351
2352 AppWidgetHostView hostView = info.boundWidget;
2353 int appWidgetId;
2354 if (hostView != null) {
2355 appWidgetId = hostView.getAppWidgetId();
2356 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2357 } else {
2358 // In this case, we either need to start an activity to get permission to bind
2359 // the widget, or we need to start an activity to configure the widget, or both.
2360 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2361 Bundle options = info.bindOptions;
2362
2363 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2364 appWidgetId, info.info, options);
2365 if (success) {
2366 addAppWidgetImpl(appWidgetId, info, null, info.info);
2367 } else {
2368 mPendingAddWidgetInfo = info.info;
2369 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2370 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2371 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2372 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2373 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2374 // TODO: we need to make sure that this accounts for the options bundle.
2375 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2376 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2377 }
2378 }
2379 }
2380
2381 void processShortcut(Intent intent) {
2382 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2383 }
2384
2385 void processWallpaper(Intent intent) {
2386 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2387 }
2388
2389 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2390 int cellY) {
2391 final FolderInfo folderInfo = new FolderInfo();
2392 folderInfo.title = getText(R.string.folder_name);
2393
2394 // Update the model
2395 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
2396 false);
2397 sFolders.put(folderInfo.id, folderInfo);
2398
2399 // Create the view
2400 FolderIcon newFolder =
2401 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2402 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2403 isWorkspaceLocked());
2404 // Force measure the new folder icon
2405 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2406 parent.getShortcutsAndWidgets().measureChild(newFolder);
2407 return newFolder;
2408 }
2409
2410 void removeFolder(FolderInfo folder) {
2411 sFolders.remove(folder.id);
2412 }
2413
2414 protected ComponentName getWallpaperPickerComponent() {
2415 if (mLauncherCallbacks != null) {
2416 return mLauncherCallbacks.getWallpaperPickerComponent();
2417 }
2418 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
2419 }
2420
2421 /**
2422 * Registers various content observers. The current implementation registers
2423 * only a favorites observer to keep track of the favorites applications.
2424 */
2425 private void registerContentObservers() {
2426 ContentResolver resolver = getContentResolver();
2427 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2428 true, mWidgetObserver);
2429 }
2430
2431 @Override
2432 public boolean dispatchKeyEvent(KeyEvent event) {
2433 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2434 switch (event.getKeyCode()) {
2435 case KeyEvent.KEYCODE_HOME:
2436 return true;
2437 case KeyEvent.KEYCODE_VOLUME_DOWN:
2438 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2439 dumpState();
2440 return true;
2441 }
2442 break;
2443 }
2444 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2445 switch (event.getKeyCode()) {
2446 case KeyEvent.KEYCODE_HOME:
2447 return true;
2448 }
2449 }
2450
2451 return super.dispatchKeyEvent(event);
2452 }
2453
2454 @Override
2455 public void onBackPressed() {
2456 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2457 return;
2458 }
2459
2460 if (LauncherAppState.getInstance().getAccessibilityDelegate().onBackPressed()) {
2461 return;
2462 }
2463
2464 if (isAppsViewVisible()) {
2465 showWorkspace(true);
2466 } else if (isWidgetsViewVisible()) {
2467 showOverviewMode(true);
2468 } else if (mWorkspace.isInOverviewMode()) {
2469 mWorkspace.exitOverviewMode(true);
2470 } else if (mWorkspace.getOpenFolder() != null) {
2471 Folder openFolder = mWorkspace.getOpenFolder();
2472 if (openFolder.isEditingName()) {
2473 openFolder.dismissEditingName();
2474 } else {
2475 closeFolder();
2476 }
2477 } else {
2478 mWorkspace.exitWidgetResizeMode();
2479
2480 // Back button is a no-op here, but give at least some feedback for the button press
2481 mWorkspace.showOutlinesTemporarily();
2482 }
2483 }
2484
2485 /**
2486 * Re-listen when widgets are reset.
2487 */
2488 private void onAppWidgetReset() {
2489 if (mAppWidgetHost != null) {
2490 mAppWidgetHost.startListening();
2491 }
2492 }
2493
2494 /**
2495 * Launches the intent referred by the clicked shortcut.
2496 *
2497 * @param v The view representing the clicked shortcut.
2498 */
2499 public void onClick(View v) {
2500 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2501 // view has detached (it's possible for this to happen if the view is removed mid touch).
2502 if (v.getWindowToken() == null) {
2503 return;
2504 }
2505
2506 if (!mWorkspace.isFinishedSwitchingState()) {
2507 return;
2508 }
2509
2510 if (v instanceof Workspace) {
2511 if (mWorkspace.isInOverviewMode()) {
2512 mWorkspace.exitOverviewMode(true);
2513 }
2514 return;
2515 }
2516
2517 if (v instanceof CellLayout) {
2518 if (mWorkspace.isInOverviewMode()) {
2519 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2520 }
2521 }
2522
2523 Object tag = v.getTag();
2524 if (tag instanceof ShortcutInfo) {
2525 onClickAppShortcut(v);
2526 } else if (tag instanceof FolderInfo) {
2527 if (v instanceof FolderIcon) {
2528 onClickFolderIcon(v);
2529 }
2530 } else if (v == mAllAppsButton) {
2531 onClickAllAppsButton(v);
2532 } else if (tag instanceof AppInfo) {
2533 startAppShortcutOrInfoActivity(v);
2534 } else if (tag instanceof LauncherAppWidgetInfo) {
2535 if (v instanceof PendingAppWidgetHostView) {
2536 onClickPendingWidget((PendingAppWidgetHostView) v);
2537 }
2538 }
2539 }
2540
2541 public void onClickPagedViewIcon(View v) {
2542 startAppShortcutOrInfoActivity(v);
2543 if (mLauncherCallbacks != null) {
2544 mLauncherCallbacks.onClickPagedViewIcon(v);
2545 }
2546 }
2547
2548 public boolean onTouch(View v, MotionEvent event) {
2549 return false;
2550 }
2551
2552 /**
2553 * Event handler for the app widget view which has not fully restored.
2554 */
2555 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2556 if (mIsSafeModeEnabled) {
2557 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2558 return;
2559 }
2560
2561 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2562 if (v.isReadyForClickSetup()) {
2563 int widgetId = info.appWidgetId;
2564 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2565 if (appWidgetInfo != null) {
2566 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2567 this, appWidgetInfo);
2568 mPendingAddInfo.copyFrom(info);
2569 mPendingAddWidgetId = widgetId;
2570
2571 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2572 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2573 }
2574 } else if (info.installProgress < 0) {
2575 // The install has not been queued
2576 final String packageName = info.providerName.getPackageName();
2577 showBrokenAppInstallDialog(packageName,
2578 new DialogInterface.OnClickListener() {
2579 public void onClick(DialogInterface dialog, int id) {
2580 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2581 }
2582 });
2583 } else {
2584 // Download has started.
2585 final String packageName = info.providerName.getPackageName();
2586 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2587 }
2588 }
2589
2590 /**
2591 * Event handler for the "grid" button that appears on the home screen, which
2592 * enters all apps mode.
2593 *
2594 * @param v The view that was clicked.
2595 */
2596 protected void onClickAllAppsButton(View v) {
2597 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2598 if (isAppsViewVisible()) {
2599 showWorkspace(true);
2600 } else {
2601 showAppsView(true /* animated */, false /* resetListToTop */);
2602 }
2603 if (mLauncherCallbacks != null) {
2604 mLauncherCallbacks.onClickAllAppsButton(v);
2605 }
2606 }
2607
2608 private void showBrokenAppInstallDialog(final String packageName,
2609 DialogInterface.OnClickListener onSearchClickListener) {
2610 new AlertDialog.Builder(this)
2611 .setTitle(R.string.abandoned_promises_title)
2612 .setMessage(R.string.abandoned_promise_explanation)
2613 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2614 .setNeutralButton(R.string.abandoned_clean_this,
2615 new DialogInterface.OnClickListener() {
2616 public void onClick(DialogInterface dialog, int id) {
2617 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2618 mWorkspace.removeAbandonedPromise(packageName, user);
2619 }
2620 })
2621 .create().show();
2622 return;
2623 }
2624
2625 /**
2626 * Event handler for an app shortcut click.
2627 *
2628 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2629 */
2630 protected void onClickAppShortcut(final View v) {
2631 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2632 Object tag = v.getTag();
2633 if (!(tag instanceof ShortcutInfo)) {
2634 throw new IllegalArgumentException("Input must be a Shortcut");
2635 }
2636
2637 // Open shortcut
2638 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2639
2640 if (shortcut.isDisabled != 0) {
2641 int error = R.string.activity_not_available;
2642 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2643 error = R.string.safemode_shortcut_error;
2644 }
2645 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2646 return;
2647 }
2648
2649 final Intent intent = shortcut.intent;
2650
2651 // Check for special shortcuts
2652 if (intent.getComponent() != null) {
2653 final String shortcutClass = intent.getComponent().getClassName();
2654
2655 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2656 MemoryDumpActivity.startDump(this);
2657 return;
2658 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2659 toggleShowWeightWatcher();
2660 return;
2661 }
2662 }
2663
2664 // Check for abandoned promise
2665 if ((v instanceof BubbleTextView)
2666 && shortcut.isPromise()
2667 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2668 showBrokenAppInstallDialog(
2669 shortcut.getTargetComponent().getPackageName(),
2670 new DialogInterface.OnClickListener() {
2671 public void onClick(DialogInterface dialog, int id) {
2672 startAppShortcutOrInfoActivity(v);
2673 }
2674 });
2675 return;
2676 }
2677
2678 // Start activities
2679 startAppShortcutOrInfoActivity(v);
2680
2681 if (mLauncherCallbacks != null) {
2682 mLauncherCallbacks.onClickAppShortcut(v);
2683 }
2684 }
2685
2686 private void startAppShortcutOrInfoActivity(View v) {
2687 Object tag = v.getTag();
2688 final ShortcutInfo shortcut;
2689 final Intent intent;
2690 if (tag instanceof ShortcutInfo) {
2691 shortcut = (ShortcutInfo) tag;
2692 intent = shortcut.intent;
2693 int[] pos = new int[2];
2694 v.getLocationOnScreen(pos);
2695 intent.setSourceBounds(new Rect(pos[0], pos[1],
2696 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2697
2698 } else if (tag instanceof AppInfo) {
2699 shortcut = null;
2700 intent = ((AppInfo) tag).intent;
2701 } else {
2702 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2703 }
2704
2705 boolean success = startActivitySafely(v, intent, tag);
2706 mStats.recordLaunch(intent, shortcut);
2707
2708 if (success && v instanceof BubbleTextView) {
2709 mWaitingForResume = (BubbleTextView) v;
2710 mWaitingForResume.setStayPressed(true);
2711 }
2712 }
2713
2714 /**
2715 * Event handler for a folder icon click.
2716 *
2717 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2718 */
2719 protected void onClickFolderIcon(View v) {
2720 if (LOGD) Log.d(TAG, "onClickFolder");
2721 if (!(v instanceof FolderIcon)){
2722 throw new IllegalArgumentException("Input must be a FolderIcon");
2723 }
2724
2725 FolderIcon folderIcon = (FolderIcon) v;
2726 final FolderInfo info = folderIcon.getFolderInfo();
2727 Folder openFolder = mWorkspace.getFolderForTag(info);
2728
2729 // If the folder info reports that the associated folder is open, then verify that
2730 // it is actually opened. There have been a few instances where this gets out of sync.
2731 if (info.opened && openFolder == null) {
2732 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2733 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2734 info.opened = false;
2735 }
2736
2737 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2738 // Close any open folder
2739 closeFolder();
2740 // Open the requested folder
2741 openFolder(folderIcon);
2742 } else {
2743 // Find the open folder...
2744 int folderScreen;
2745 if (openFolder != null) {
2746 folderScreen = mWorkspace.getPageForView(openFolder);
2747 // .. and close it
2748 closeFolder(openFolder);
2749 if (folderScreen != mWorkspace.getCurrentPage()) {
2750 // Close any folder open on the current screen
2751 closeFolder();
2752 // Pull the folder onto this screen
2753 openFolder(folderIcon);
2754 }
2755 }
2756 }
2757
2758 if (mLauncherCallbacks != null) {
2759 mLauncherCallbacks.onClickFolderIcon(v);
2760 }
2761 }
2762
2763 /**
2764 * Event handler for the (Add) Widgets button that appears after a long press
2765 * on the home screen.
2766 */
2767 protected void onClickAddWidgetButton(View view) {
2768 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2769 if (mIsSafeModeEnabled) {
2770 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2771 } else {
2772 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2773 if (mLauncherCallbacks != null) {
2774 mLauncherCallbacks.onClickAddWidgetButton(view);
2775 }
2776 }
2777 }
2778
2779 /**
2780 * Event handler for the wallpaper picker button that appears after a long press
2781 * on the home screen.
2782 */
2783 protected void onClickWallpaperPicker(View v) {
2784 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2785 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
2786 pickWallpaper.setComponent(getWallpaperPickerComponent());
2787 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
2788
2789 if (mLauncherCallbacks != null) {
2790 mLauncherCallbacks.onClickWallpaperPicker(v);
2791 }
2792 }
2793
2794 /**
2795 * Event handler for a click on the settings button that appears after a long press
2796 * on the home screen.
2797 */
2798 protected void onClickSettingsButton(View v) {
2799 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2800 if (mLauncherCallbacks != null) {
2801 mLauncherCallbacks.onClickSettingsButton(v);
2802 }
2803 }
2804
2805 public void onTouchDownAllAppsButton(View v) {
2806 // Provide the same haptic feedback that the system offers for virtual keys.
2807 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2808 }
2809
2810 public void performHapticFeedbackOnTouchDown(View v) {
2811 // Provide the same haptic feedback that the system offers for virtual keys.
2812 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2813 }
2814
2815 public View.OnTouchListener getHapticFeedbackTouchListener() {
2816 if (mHapticFeedbackTouchListener == null) {
2817 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2818 @Override
2819 public boolean onTouch(View v, MotionEvent event) {
2820 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2821 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2822 }
2823 return false;
2824 }
2825 };
2826 }
2827 return mHapticFeedbackTouchListener;
2828 }
2829
2830 public void onDragStarted(View view) {
2831 if (isOnCustomContent()) {
2832 // Custom content screen doesn't participate in drag and drop. If on custom
2833 // content screen, move to default.
2834 moveWorkspaceToDefaultScreen();
2835 }
2836
2837 if (mLauncherCallbacks != null) {
2838 mLauncherCallbacks.onDragStarted(view);
2839 }
2840 }
2841
2842 /**
2843 * Called when the user stops interacting with the launcher.
2844 * This implies that the user is now on the homescreen and is not doing housekeeping.
2845 */
2846 protected void onInteractionEnd() {
2847 if (mLauncherCallbacks != null) {
2848 mLauncherCallbacks.onInteractionEnd();
2849 }
2850 }
2851
2852 /**
2853 * Called when the user starts interacting with the launcher.
2854 * The possible interactions are:
2855 * - open all apps
2856 * - reorder an app shortcut, or a widget
2857 * - open the overview mode.
2858 * This is a good time to stop doing things that only make sense
2859 * when the user is on the homescreen and not doing housekeeping.
2860 */
2861 protected void onInteractionBegin() {
2862 if (mLauncherCallbacks != null) {
2863 mLauncherCallbacks.onInteractionBegin();
2864 }
2865 }
2866
2867 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2868 try {
2869 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2870 launcherApps.showAppDetailsForProfile(componentName, user);
2871 } catch (SecurityException e) {
2872 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2873 Log.e(TAG, "Launcher does not have permission to launch settings");
2874 } catch (ActivityNotFoundException e) {
2875 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2876 Log.e(TAG, "Unable to launch settings");
2877 }
2878 }
2879
2880 // returns true if the activity was started
2881 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2882 UserHandleCompat user) {
2883 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2884 // System applications cannot be installed. For now, show a toast explaining that.
2885 // We may give them the option of disabling apps this way.
2886 int messageId = R.string.uninstall_system_app_text;
2887 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2888 return false;
2889 } else {
2890 String packageName = componentName.getPackageName();
2891 String className = componentName.getClassName();
2892 Intent intent = new Intent(
2893 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2894 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2895 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2896 if (user != null) {
2897 user.addToIntent(intent, Intent.EXTRA_USER);
2898 }
2899 startActivity(intent);
2900 return true;
2901 }
2902 }
2903
2904 boolean startActivity(View v, Intent intent, Object tag) {
2905 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2906 try {
2907 // Only launch using the new animation if the shortcut has not opted out (this is a
2908 // private contract between launcher and may be ignored in the future).
2909 boolean useLaunchAnimation = (v != null) &&
2910 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2911 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2912 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2913
2914 UserHandleCompat user = null;
2915 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2916 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2917 user = userManager.getUserForSerialNumber(serialNumber);
2918 }
2919
2920 Bundle optsBundle = null;
2921 if (useLaunchAnimation) {
2922 ActivityOptions opts = null;
2923 if (sClipRevealMethod != null) {
2924 // TODO: call method directly when Launcher3 can depend on M APIs
2925 int left = 0, top = 0;
2926 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2927 if (v instanceof TextView) {
2928 // Launch from center of icon, not entire view
2929 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2930 if (icon != null) {
2931 Rect bounds = icon.getBounds();
2932 left = (width - bounds.width()) / 2;
2933 top = v.getPaddingTop();
2934 width = bounds.width();
2935 height = bounds.height();
2936 }
2937 }
2938 try {
2939 opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
2940 left, top, width, height);
2941 } catch (IllegalAccessException e) {
2942 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2943 sClipRevealMethod = null;
2944 } catch (InvocationTargetException e) {
2945 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2946 sClipRevealMethod = null;
2947 }
2948 }
2949 if (opts == null) {
2950 opts = Utilities.isLmpOrAbove() ?
2951 ActivityOptions.makeCustomAnimation(this,
2952 R.anim.task_open_enter, R.anim.no_anim) :
2953 ActivityOptions.makeScaleUpAnimation(v, 0, 0,
2954 v.getMeasuredWidth(), v.getMeasuredHeight());
2955 }
2956 optsBundle = opts.toBundle();
2957 }
2958
2959 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2960 // Could be launching some bookkeeping activity
2961 startActivity(intent, optsBundle);
2962 } else {
2963 // TODO Component can be null when shortcuts are supported for secondary user
2964 launcherApps.startActivityForProfile(intent.getComponent(), user,
2965 intent.getSourceBounds(), optsBundle);
2966 }
2967 return true;
2968 } catch (SecurityException e) {
2969 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2970 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2971 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2972 "or use the exported attribute for this activity. "
2973 + "tag="+ tag + " intent=" + intent, e);
2974 }
2975 return false;
2976 }
2977
2978 boolean startActivitySafely(View v, Intent intent, Object tag) {
2979 boolean success = false;
2980 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2981 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2982 return false;
2983 }
2984 try {
2985 success = startActivity(v, intent, tag);
2986 } catch (ActivityNotFoundException e) {
2987 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2988 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2989 }
2990 return success;
2991 }
2992
2993 /**
2994 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2995 * in the DragLayer in the exact absolute location of the original FolderIcon.
2996 */
2997 private void copyFolderIconToImage(FolderIcon fi) {
2998 final int width = fi.getMeasuredWidth();
2999 final int height = fi.getMeasuredHeight();
3000
3001 // Lazy load ImageView, Bitmap and Canvas
3002 if (mFolderIconImageView == null) {
3003 mFolderIconImageView = new ImageView(this);
3004 }
3005 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3006 mFolderIconBitmap.getHeight() != height) {
3007 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3008 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3009 }
3010
3011 DragLayer.LayoutParams lp;
3012 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3013 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3014 } else {
3015 lp = new DragLayer.LayoutParams(width, height);
3016 }
3017
3018 // The layout from which the folder is being opened may be scaled, adjust the starting
3019 // view size by this scale factor.
3020 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3021 lp.customPosition = true;
3022 lp.x = mRectForFolderAnimation.left;
3023 lp.y = mRectForFolderAnimation.top;
3024 lp.width = (int) (scale * width);
3025 lp.height = (int) (scale * height);
3026
3027 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3028 fi.draw(mFolderIconCanvas);
3029 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3030 if (fi.getFolder() != null) {
3031 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3032 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3033 }
3034 // Just in case this image view is still in the drag layer from a previous animation,
3035 // we remove it and re-add it.
3036 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3037 mDragLayer.removeView(mFolderIconImageView);
3038 }
3039 mDragLayer.addView(mFolderIconImageView, lp);
3040 if (fi.getFolder() != null) {
3041 fi.getFolder().bringToFront();
3042 }
3043 }
3044
3045 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3046 if (fi == null) return;
3047 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3048 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3049 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3050
3051 FolderInfo info = (FolderInfo) fi.getTag();
3052 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3053 CellLayout cl = (CellLayout) fi.getParent().getParent();
3054 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3055 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3056 }
3057
3058 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3059 copyFolderIconToImage(fi);
3060 fi.setVisibility(View.INVISIBLE);
3061
3062 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3063 scaleX, scaleY);
3064 if (Utilities.isLmpOrAbove()) {
3065 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3066 }
3067 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3068 oa.start();
3069 }
3070
3071 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3072 if (fi == null) return;
3073 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3074 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3075 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3076
3077 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3078
3079 // We remove and re-draw the FolderIcon in-case it has changed
3080 mDragLayer.removeView(mFolderIconImageView);
3081 copyFolderIconToImage(fi);
3082 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3083 scaleX, scaleY);
3084 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3085 oa.addListener(new AnimatorListenerAdapter() {
3086 @Override
3087 public void onAnimationEnd(Animator animation) {
3088 if (cl != null) {
3089 cl.clearFolderLeaveBehind();
3090 // Remove the ImageView copy of the FolderIcon and make the original visible.
3091 mDragLayer.removeView(mFolderIconImageView);
3092 fi.setVisibility(View.VISIBLE);
3093 }
3094 }
3095 });
3096 oa.start();
3097 }
3098
3099 /**
3100 * Opens the user folder described by the specified tag. The opening of the folder
3101 * is animated relative to the specified View. If the View is null, no animation
3102 * is played.
3103 *
3104 * @param folderInfo The FolderInfo describing the folder to open.
3105 */
3106 public void openFolder(FolderIcon folderIcon) {
3107 Folder folder = folderIcon.getFolder();
3108 FolderInfo info = folder.mInfo;
3109
3110 info.opened = true;
3111
3112 // Just verify that the folder hasn't already been added to the DragLayer.
3113 // There was a one-off crash where the folder had a parent already.
3114 if (folder.getParent() == null) {
3115 mDragLayer.addView(folder);
3116 mDragController.addDropTarget((DropTarget) folder);
3117 } else {
3118 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3119 folder.getParent() + ").");
3120 }
3121 folder.animateOpen();
3122 growAndFadeOutFolderIcon(folderIcon);
3123
3124 // Notify the accessibility manager that this folder "window" has appeared and occluded
3125 // the workspace items
3126 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3127 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3128 }
3129
3130 public void closeFolder() {
3131 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3132 if (folder != null) {
3133 if (folder.isEditingName()) {
3134 folder.dismissEditingName();
3135 }
3136 closeFolder(folder);
3137 }
3138 }
3139
3140 void closeFolder(Folder folder) {
3141 folder.getInfo().opened = false;
3142
3143 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3144 if (parent != null) {
3145 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3146 shrinkAndFadeInFolderIcon(fi);
3147 }
3148 folder.animateClosed();
3149
3150 // Notify the accessibility manager that this folder "window" has disappeard and no
3151 // longer occludeds the workspace items
3152 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3153 }
3154
3155 public boolean onLongClick(View v) {
3156 if (!isDraggingEnabled()) return false;
3157 if (isWorkspaceLocked()) return false;
3158 if (mState != State.WORKSPACE) return false;
3159
3160 if (v instanceof Workspace) {
3161 if (!mWorkspace.isInOverviewMode()) {
3162 if (mWorkspace.enterOverviewMode()) {
3163 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3164 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3165 return true;
3166 } else {
3167 return false;
3168 }
3169 } else {
3170 return false;
3171 }
3172 }
3173
3174 CellLayout.CellInfo longClickCellInfo = null;
3175 View itemUnderLongClick = null;
3176 if (v.getTag() instanceof ItemInfo) {
3177 ItemInfo info = (ItemInfo) v.getTag();
3178 longClickCellInfo = new CellLayout.CellInfo(v, info);
3179 itemUnderLongClick = longClickCellInfo.cell;
3180 resetAddInfo();
3181 }
3182
3183 // The hotseat touch handling does not go through Workspace, and we always allow long press
3184 // on hotseat items.
3185 final boolean inHotseat = isHotseatLayout(v);
3186 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3187 if (allowLongPress && !mDragController.isDragging()) {
3188 if (itemUnderLongClick == null) {
3189 // User long pressed on empty space
3190 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3191 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3192 if (mWorkspace.isInOverviewMode()) {
3193 mWorkspace.startReordering(v);
3194 } else {
3195 mWorkspace.enterOverviewMode();
3196 }
3197 } else {
3198 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3199 mHotseat.getOrderInHotseat(
3200 longClickCellInfo.cellX,
3201 longClickCellInfo.cellY));
3202 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3203 // User long pressed on an item
3204 mWorkspace.startDrag(longClickCellInfo);
3205 }
3206 }
3207 }
3208 return true;
3209 }
3210
3211 boolean isHotseatLayout(View layout) {
3212 return mHotseat != null && layout != null &&
3213 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3214 }
3215
3216 /**
3217 * Returns the CellLayout of the specified container at the specified screen.
3218 */
3219 public CellLayout getCellLayout(long container, long screenId) {
3220 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3221 if (mHotseat != null) {
3222 return mHotseat.getLayout();
3223 } else {
3224 return null;
3225 }
3226 } else {
3227 return mWorkspace.getScreenWithId(screenId);
3228 }
3229 }
3230
3231 /**
3232 * For overridden classes.
3233 */
3234 public boolean isAllAppsVisible() {
3235 return isAppsViewVisible();
3236 }
3237
3238 public boolean isAppsViewVisible() {
3239 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3240 }
3241
3242 public boolean isWidgetsViewVisible() {
3243 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3244 }
3245
3246 private void setWorkspaceBackground(boolean workspace) {
3247 mLauncherView.setBackground(workspace ?
3248 mWorkspaceBackgroundDrawable : null);
3249 }
3250
3251 protected void changeWallpaperVisiblity(boolean visible) {
3252 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3253 int curflags = getWindow().getAttributes().flags
3254 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3255 if (wpflags != curflags) {
3256 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3257 }
3258 setWorkspaceBackground(visible);
3259 }
3260
3261 @Override
3262 public void onTrimMemory(int level) {
3263 super.onTrimMemory(level);
3264 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3265 // The widget preview db can result in holding onto over
3266 // 3MB of memory for caching which isn't necessary.
3267 SQLiteDatabase.releaseMemory();
3268
3269 // This clears all widget bitmaps from the widget tray
3270 if (mAppsCustomizeTabHost != null) {
3271 mAppsCustomizeTabHost.trimMemory();
3272 }
3273 }
3274 }
3275
3276 @Override
3277 public void onStateTransitionHideSearchBar() {
3278 // Hide the search bar
3279 if (mSearchDropTargetBar != null) {
3280 mSearchDropTargetBar.hideSearchBar(false /* animated */);
3281 }
3282 }
3283
3284 protected void showWorkspace(boolean animated) {
3285 showWorkspace(animated, null);
3286 }
3287
3288 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3289 if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) {
3290 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3291 mWorkspace.setVisibility(View.VISIBLE);
3292 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
3293 animated, onCompleteRunnable);
3294
3295 // Show the search bar (only animate if we were showing the drop target bar in spring
3296 // loaded mode)
3297 if (mSearchDropTargetBar != null) {
3298 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3299 }
3300
3301 // Set focus to the AppsCustomize button
3302 if (mAllAppsButton != null) {
3303 mAllAppsButton.requestFocus();
3304 }
3305 }
3306
3307 // Change the state *after* we've called all the transition code
3308 mState = State.WORKSPACE;
3309
3310 // Resume the auto-advance of widgets
3311 mUserPresent = true;
3312 updateAutoAdvanceState();
3313
3314 // Send an accessibility event to announce the context change
3315 getWindow().getDecorView()
3316 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3317
3318 onWorkspaceShown(animated);
3319 }
3320
3321 void showOverviewMode(boolean animated) {
3322 mWorkspace.setVisibility(View.VISIBLE);
3323 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
3324 animated, null /* onCompleteRunnable */);
3325 mState = State.WORKSPACE;
3326 onWorkspaceShown(animated);
3327 }
3328
3329 public void onWorkspaceShown(boolean animated) {
3330 }
3331
3332 /**
3333 * Shows the apps view.
3334 */
3335 void showAppsView(boolean animated, boolean resetListToTop) {
3336 if (resetListToTop) {
3337 mAppsView.scrollToTop();
3338 }
3339 showAppsOrWidgets(animated, State.APPS);
3340 }
3341
3342 /**
3343 * Shows the widgets view.
3344 */
3345 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3346 if (resetPageToZero) {
3347 mAppsCustomizeTabHost.reset();
3348 }
3349 showAppsOrWidgets(animated, State.WIDGETS);
3350 mAppsCustomizeTabHost.post(new Runnable() {
3351 @Override
3352 public void run() {
3353 // We post this in-case the all apps view isn't yet constructed.
3354 mAppsCustomizeTabHost.requestFocus();
3355 }
3356 });
3357 }
3358
3359 /**
3360 * Sets up the transition to show the apps/widgets view.
3361 */
3362 private void showAppsOrWidgets(boolean animated, State toState) {
3363 if (mState != State.WORKSPACE) return;
3364 if (toState != State.APPS && toState != State.WIDGETS) return;
3365
3366 if (toState == State.APPS) {
3367 mStateTransitionAnimation.startAnimationToAllApps(animated);
3368 } else {
3369 mStateTransitionAnimation.startAnimationToWidgets(animated);
3370 }
3371
3372 // Change the state *after* we've called all the transition code
3373 mState = toState;
3374
3375 // Pause the auto-advance of widgets until we are out of AllApps
3376 mUserPresent = false;
3377 updateAutoAdvanceState();
3378 closeFolder();
3379
3380 // Send an accessibility event to announce the context change
3381 getWindow().getDecorView()
3382 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3383 }
3384
3385 void enterSpringLoadedDragMode() {
3386 if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
3387 mState == State.WIDGETS_SPRING_LOADED) {
3388 return;
3389 }
3390
3391 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
3392 true /* animated */, null /* onCompleteRunnable */);
3393 mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3394 }
3395
3396 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3397 final Runnable onCompleteRunnable) {
3398 if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
3399
3400 mHandler.postDelayed(new Runnable() {
3401 @Override
3402 public void run() {
3403 if (successfulDrop) {
3404 // Before we show workspace, hide all apps again because
3405 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3406 // clean up our state transition functions
3407 mAppsCustomizeTabHost.setVisibility(View.GONE);
3408 showWorkspace(true, onCompleteRunnable);
3409 } else {
3410 exitSpringLoadedDragMode();
3411 }
3412 }
3413 }, delay);
3414 }
3415
3416 void exitSpringLoadedDragMode() {
3417 if (mState == State.APPS_SPRING_LOADED) {
3418 mStateTransitionAnimation.startAnimationToAllApps(true /* animated */);
3419 mState = State.APPS;
3420 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3421 mStateTransitionAnimation.startAnimationToWidgets(true /* animated */);
3422 mState = State.WIDGETS;
3423 }
3424 // Otherwise, we are not in spring loaded mode, so don't do anything.
3425 }
3426
3427 void lockAllApps() {
3428 // TODO
3429 }
3430
3431 void unlockAllApps() {
3432 // TODO
3433 }
3434
3435 protected void disableVoiceButtonProxy(boolean disable) {
3436 // NO-OP
3437 }
3438
3439 public View getOrCreateQsbBar() {
3440 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
3441 return mLauncherCallbacks.getQsbBar();
3442 }
3443
3444 if (mQsb == null) {
3445 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3446 if (searchProvider == null) {
3447 return null;
3448 }
3449
3450 Bundle opts = new Bundle();
3451 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
3452 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
3453
3454 SharedPreferences sp = getSharedPreferences(
3455 LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
3456 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3457 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3458 if (!searchProvider.provider.flattenToString().equals(
3459 sp.getString(QSB_WIDGET_PROVIDER, null))
3460 || (widgetInfo == null)
3461 || !widgetInfo.provider.equals(searchProvider.provider)) {
3462 // A valid widget is not already bound.
3463 if (widgetId > -1) {
3464 mAppWidgetHost.deleteAppWidgetId(widgetId);
3465 widgetId = -1;
3466 }
3467
3468 // Try to bind a new widget
3469 widgetId = mAppWidgetHost.allocateAppWidgetId();
3470
3471 if (!AppWidgetManagerCompat.getInstance(this)
3472 .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
3473 mAppWidgetHost.deleteAppWidgetId(widgetId);
3474 widgetId = -1;
3475 }
3476
3477 sp.edit()
3478 .putInt(QSB_WIDGET_ID, widgetId)
3479 .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
3480 .commit();
3481 }
3482
3483 if (widgetId != -1) {
3484 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3485 mQsb.updateAppWidgetOptions(opts);
3486 mQsb.setPadding(0, 0, 0, 0);
3487 mSearchDropTargetBar.addView(mQsb);
3488 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3489 }
3490 }
3491 return mQsb;
3492 }
3493
3494 @Override
3495 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3496 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3497 final List<CharSequence> text = event.getText();
3498 text.clear();
3499 // Populate event with a fake title based on the current state.
3500 if (mState == State.APPS) {
3501 text.add("Apps");
3502 } else if (mState == State.WIDGETS) {
3503 text.add("Widgets");
3504 } else {
3505 text.add(getString(R.string.all_apps_home_button_label));
3506 }
3507 return result;
3508 }
3509
3510 /**
3511 * Receives notifications when system dialogs are to be closed.
3512 */
3513 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3514 @Override
3515 public void onReceive(Context context, Intent intent) {
3516 closeSystemDialogs();
3517 }
3518 }
3519
3520 /**
3521 * Receives notifications whenever the appwidgets are reset.
3522 */
3523 private class AppWidgetResetObserver extends ContentObserver {
3524 public AppWidgetResetObserver() {
3525 super(new Handler());
3526 }
3527
3528 @Override
3529 public void onChange(boolean selfChange) {
3530 onAppWidgetReset();
3531 }
3532 }
3533
3534 /**
3535 * If the activity is currently paused, signal that we need to run the passed Runnable
3536 * in onResume.
3537 *
3538 * This needs to be called from incoming places where resources might have been loaded
3539 * while we are paused. That is becaues the Configuration might be wrong
3540 * when we're not running, and if it comes back to what it was when we
3541 * were paused, we are not restarted.
3542 *
3543 * Implementation of the method from LauncherModel.Callbacks.
3544 *
3545 * @return true if we are currently paused. The caller might be able to
3546 * skip some work in that case since we will come back again.
3547 */
3548 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3549 if (mPaused) {
3550 Log.i(TAG, "Deferring update until onResume");
3551 if (deletePreviousRunnables) {
3552 while (mBindOnResumeCallbacks.remove(run)) {
3553 }
3554 }
3555 mBindOnResumeCallbacks.add(run);
3556 return true;
3557 } else {
3558 return false;
3559 }
3560 }
3561
3562 private boolean waitUntilResume(Runnable run) {
3563 return waitUntilResume(run, false);
3564 }
3565
3566 public void addOnResumeCallback(Runnable run) {
3567 mOnResumeCallbacks.add(run);
3568 }
3569
3570 /**
3571 * If the activity is currently paused, signal that we need to re-run the loader
3572 * in onResume.
3573 *
3574 * This needs to be called from incoming places where resources might have been loaded
3575 * while we are paused. That is becaues the Configuration might be wrong
3576 * when we're not running, and if it comes back to what it was when we
3577 * were paused, we are not restarted.
3578 *
3579 * Implementation of the method from LauncherModel.Callbacks.
3580 *
3581 * @return true if we are currently paused. The caller might be able to
3582 * skip some work in that case since we will come back again.
3583 */
3584 public boolean setLoadOnResume() {
3585 if (mPaused) {
3586 Log.i(TAG, "setLoadOnResume");
3587 mOnResumeNeedsLoad = true;
3588 return true;
3589 } else {
3590 return false;
3591 }
3592 }
3593
3594 /**
3595 * Implementation of the method from LauncherModel.Callbacks.
3596 */
3597 public int getCurrentWorkspaceScreen() {
3598 if (mWorkspace != null) {
3599 return mWorkspace.getCurrentPage();
3600 } else {
3601 return SCREEN_COUNT / 2;
3602 }
3603 }
3604
3605 /**
3606 * Refreshes the shortcuts shown on the workspace.
3607 *
3608 * Implementation of the method from LauncherModel.Callbacks.
3609 */
3610 public void startBinding() {
3611 setWorkspaceLoading(true);
3612
3613 // If we're starting binding all over again, clear any bind calls we'd postponed in
3614 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3615 // from scratch again
3616 mBindOnResumeCallbacks.clear();
3617
3618 // Clear the workspace because it's going to be rebound
3619 mWorkspace.clearDropTargets();
3620 mWorkspace.removeAllWorkspaceScreens();
3621
3622 mWidgetsToAdvance.clear();
3623 if (mHotseat != null) {
3624 mHotseat.resetLayout();
3625 }
3626 }
3627
3628 @Override
3629 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3630 bindAddScreens(orderedScreenIds);
3631
3632 // If there are no screens, we need to have an empty screen
3633 if (orderedScreenIds.size() == 0) {
3634 mWorkspace.addExtraEmptyScreen();
3635 }
3636
3637 // Create the custom content page (this call updates mDefaultScreen which calls
3638 // setCurrentPage() so ensure that all pages are added before calling this).
3639 if (hasCustomContentToLeft()) {
3640 mWorkspace.createCustomContentContainer();
3641 populateCustomContentContainer();
3642 }
3643 }
3644
3645 @Override
3646 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3647 // Log to disk
3648 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3649 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3650 TextUtils.join(", ", orderedScreenIds), true);
3651 int count = orderedScreenIds.size();
3652 for (int i = 0; i < count; i++) {
3653 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3654 }
3655 }
3656
3657 @Override
3658 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
3659 final long screenId, final int[] cell, final int spanX, final int spanY) {
3660 showWorkspace(true, new Runnable() {
3661
3662 @Override
3663 public void run() {
3664 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
3665 addPendingItem(info, container, screenId, cell, spanX, spanY);
3666 }
3667 });
3668 }
3669
3670 private boolean shouldShowWeightWatcher() {
3671 String spKey = LauncherAppState.getSharedPreferencesKey();
3672 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3673 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3674
3675 return show;
3676 }
3677
3678 private void toggleShowWeightWatcher() {
3679 String spKey = LauncherAppState.getSharedPreferencesKey();
3680 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3681 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3682
3683 show = !show;
3684
3685 SharedPreferences.Editor editor = sp.edit();
3686 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3687 editor.commit();
3688
3689 if (mWeightWatcher != null) {
3690 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3691 }
3692 }
3693
3694 public void bindAppsAdded(final ArrayList<Long> newScreens,
3695 final ArrayList<ItemInfo> addNotAnimated,
3696 final ArrayList<ItemInfo> addAnimated,
3697 final ArrayList<AppInfo> addedApps) {
3698 Runnable r = new Runnable() {
3699 public void run() {
3700 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3701 }
3702 };
3703 if (waitUntilResume(r)) {
3704 return;
3705 }
3706
3707 // Add the new screens
3708 if (newScreens != null) {
3709 bindAddScreens(newScreens);
3710 }
3711
3712 // We add the items without animation on non-visible pages, and with
3713 // animations on the new page (which we will try and snap to).
3714 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3715 bindItems(addNotAnimated, 0,
3716 addNotAnimated.size(), false);
3717 }
3718 if (addAnimated != null && !addAnimated.isEmpty()) {
3719 bindItems(addAnimated, 0,
3720 addAnimated.size(), true);
3721 }
3722
3723 // Remove the extra empty screen
3724 mWorkspace.removeExtraEmptyScreen(false, false);
3725
3726 if (addedApps != null && mAppsView != null) {
3727 mAppsView.addApps(addedApps);
3728 }
3729 }
3730
3731 /**
3732 * Bind the items start-end from the list.
3733 *
3734 * Implementation of the method from LauncherModel.Callbacks.
3735 */
3736 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3737 final boolean forceAnimateIcons) {
3738 Runnable r = new Runnable() {
3739 public void run() {
3740 bindItems(shortcuts, start, end, forceAnimateIcons);
3741 }
3742 };
3743 if (waitUntilResume(r)) {
3744 return;
3745 }
3746
3747 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3748 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3749 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3750 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3751 Workspace workspace = mWorkspace;
3752 long newShortcutsScreenId = -1;
3753 for (int i = start; i < end; i++) {
3754 final ItemInfo item = shortcuts.get(i);
3755
3756 // Short circuit if we are loading dock items for a configuration which has no dock
3757 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3758 mHotseat == null) {
3759 continue;
3760 }
3761
3762 switch (item.itemType) {
3763 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3764 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3765 ShortcutInfo info = (ShortcutInfo) item;
3766 View shortcut = createShortcut(info);
3767
3768 /*
3769 * TODO: FIX collision case
3770 */
3771 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3772 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3773 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3774 View v = cl.getChildAt(item.cellX, item.cellY);
3775 Object tag = v.getTag();
3776 String desc = "Collision while binding workspace item: " + item
3777 + ". Collides with " + tag;
3778 if (LauncherAppState.isDogfoodBuild()) {
3779 throw (new RuntimeException(desc));
3780 } else {
3781 Log.d(TAG, desc);
3782 }
3783 }
3784 }
3785
3786 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3787 item.cellY, 1, 1);
3788 if (animateIcons) {
3789 // Animate all the applications up now
3790 shortcut.setAlpha(0f);
3791 shortcut.setScaleX(0f);
3792 shortcut.setScaleY(0f);
3793 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3794 newShortcutsScreenId = item.screenId;
3795 }
3796 break;
3797 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3798 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3799 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3800 (FolderInfo) item, mIconCache);
3801 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3802 item.cellY, 1, 1);
3803 break;
3804 default:
3805 throw new RuntimeException("Invalid Item Type");
3806 }
3807 }
3808
3809 if (animateIcons) {
3810 // Animate to the correct page
3811 if (newShortcutsScreenId > -1) {
3812 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3813 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3814 final Runnable startBounceAnimRunnable = new Runnable() {
3815 public void run() {
3816 anim.playTogether(bounceAnims);
3817 anim.start();
3818 }
3819 };
3820 if (newShortcutsScreenId != currentScreenId) {
3821 // We post the animation slightly delayed to prevent slowdowns
3822 // when we are loading right after we return to launcher.
3823 mWorkspace.postDelayed(new Runnable() {
3824 public void run() {
3825 if (mWorkspace != null) {
3826 mWorkspace.snapToPage(newScreenIndex);
3827 mWorkspace.postDelayed(startBounceAnimRunnable,
3828 NEW_APPS_ANIMATION_DELAY);
3829 }
3830 }
3831 }, NEW_APPS_PAGE_MOVE_DELAY);
3832 } else {
3833 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3834 }
3835 }
3836 }
3837 workspace.requestLayout();
3838 }
3839
3840 /**
3841 * Implementation of the method from LauncherModel.Callbacks.
3842 */
3843 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3844 Runnable r = new Runnable() {
3845 public void run() {
3846 bindFolders(folders);
3847 }
3848 };
3849 if (waitUntilResume(r)) {
3850 return;
3851 }
3852 sFolders.clear();
3853 sFolders.putAll(folders);
3854 }
3855
3856 /**
3857 * Add the views for a widget to the workspace.
3858 *
3859 * Implementation of the method from LauncherModel.Callbacks.
3860 */
3861 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3862 Runnable r = new Runnable() {
3863 public void run() {
3864 bindAppWidget(item);
3865 }
3866 };
3867 if (waitUntilResume(r)) {
3868 return;
3869 }
3870
3871 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3872 if (DEBUG_WIDGETS) {
3873 Log.d(TAG, "bindAppWidget: " + item);
3874 }
3875 final Workspace workspace = mWorkspace;
3876
3877 LauncherAppWidgetProviderInfo appWidgetInfo =
3878 LauncherModel.getProviderInfo(this, item.providerName);
3879
3880 if (!mIsSafeModeEnabled
3881 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
3882 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
3883 if (appWidgetInfo == null) {
3884 if (DEBUG_WIDGETS) {
3885 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3886 + " belongs to component " + item.providerName
3887 + ", as the povider is null");
3888 }
3889 LauncherModel.deleteItemFromDatabase(this, item);
3890 return;
3891 }
3892 // Note: This assumes that the id remap broadcast is received before this step.
3893 // If that is not the case, the id remap will be ignored and user may see the
3894 // click to setup view.
3895 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
3896 pendingInfo.spanX = item.spanX;
3897 pendingInfo.spanY = item.spanY;
3898 pendingInfo.minSpanX = item.minSpanX;
3899 pendingInfo.minSpanY = item.minSpanY;
3900 Bundle options =
3901 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
3902
3903 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3904 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
3905 newWidgetId, appWidgetInfo, options);
3906
3907 // TODO consider showing a permission dialog when the widget is clicked.
3908 if (!success) {
3909 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3910 if (DEBUG_WIDGETS) {
3911 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3912 + " belongs to component " + item.providerName
3913 + ", as the launcher is unable to bing a new widget id");
3914 }
3915 LauncherModel.deleteItemFromDatabase(this, item);
3916 return;
3917 }
3918
3919 item.appWidgetId = newWidgetId;
3920
3921 // If the widget has a configure activity, it is still needs to set it up, otherwise
3922 // the widget is ready to go.
3923 item.restoreStatus = (appWidgetInfo.configure == null)
3924 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
3925 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3926
3927 LauncherModel.updateItemInDatabase(this, item);
3928 }
3929
3930 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
3931 final int appWidgetId = item.appWidgetId;
3932 if (DEBUG_WIDGETS) {
3933 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
3934 + appWidgetInfo.provider);
3935 }
3936
3937 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3938 } else {
3939 appWidgetInfo = null;
3940 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
3941 mIsSafeModeEnabled);
3942 view.updateIcon(mIconCache);
3943 item.hostView = view;
3944 item.hostView.updateAppWidget(null);
3945 item.hostView.setOnClickListener(this);
3946 }
3947
3948 item.hostView.setTag(item);
3949 item.onBindAppWidget(this);
3950
3951 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
3952 item.cellY, item.spanX, item.spanY, false);
3953 if (!item.isCustomWidget()) {
3954 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3955 }
3956
3957 workspace.requestLayout();
3958
3959 if (DEBUG_WIDGETS) {
3960 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
3961 + (SystemClock.uptimeMillis()-start) + "ms");
3962 }
3963 }
3964
3965 /**
3966 * Restores a pending widget.
3967 *
3968 * @param appWidgetId The app widget id
3969 * @param cellInfo The position on screen where to create the widget.
3970 */
3971 private void completeRestoreAppWidget(final int appWidgetId) {
3972 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
3973 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
3974 Log.e(TAG, "Widget update called, when the widget no longer exists.");
3975 return;
3976 }
3977
3978 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
3979 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
3980
3981 mWorkspace.reinflateWidgetsIfNecessary();
3982 LauncherModel.updateItemInDatabase(this, info);
3983 }
3984
3985 public void onPageBoundSynchronously(int page) {
3986 mSynchronouslyBoundPages.add(page);
3987 }
3988
3989 /**
3990 * Callback saying that there aren't any more items to bind.
3991 *
3992 * Implementation of the method from LauncherModel.Callbacks.
3993 */
3994 public void finishBindingItems() {
3995 Runnable r = new Runnable() {
3996 public void run() {
3997 finishBindingItems();
3998 }
3999 };
4000 if (waitUntilResume(r)) {
4001 return;
4002 }
4003 if (mSavedState != null) {
4004 if (!mWorkspace.hasFocus()) {
4005 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4006 }
4007 mSavedState = null;
4008 }
4009
4010 mWorkspace.restoreInstanceStateForRemainingPages();
4011
4012 setWorkspaceLoading(false);
4013 sendLoadingCompleteBroadcastIfNecessary();
4014
4015 // If we received the result of any pending adds while the loader was running (e.g. the
4016 // widget configuration forced an orientation change), process them now.
4017 if (sPendingAddItem != null) {
4018 final long screenId = completeAdd(sPendingAddItem);
4019
4020 // TODO: this moves the user to the page where the pending item was added. Ideally,
4021 // the screen would be guaranteed to exist after bind, and the page would be set through
4022 // the workspace restore process.
4023 mWorkspace.post(new Runnable() {
4024 @Override
4025 public void run() {
4026 mWorkspace.snapToScreenId(screenId);
4027 }
4028 });
4029 sPendingAddItem = null;
4030 }
4031
4032 PackageInstallerCompat.getInstance(this).onFinishBind();
4033
4034 if (mLauncherCallbacks != null) {
4035 mLauncherCallbacks.finishBindingItems(false);
4036 }
4037 }
4038
4039 private void sendLoadingCompleteBroadcastIfNecessary() {
4040 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4041 String permission =
4042 getResources().getString(R.string.receive_first_load_broadcast_permission);
4043 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4044 sendBroadcast(intent, permission);
4045 SharedPreferences.Editor editor = mSharedPrefs.edit();
4046 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4047 editor.apply();
4048 }
4049 }
4050
4051 public boolean isAllAppsButtonRank(int rank) {
4052 if (mHotseat != null) {
4053 return mHotseat.isAllAppsButtonRank(rank);
4054 }
4055 return false;
4056 }
4057
4058 private boolean canRunNewAppsAnimation() {
4059 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4060 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4061 }
4062
4063 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4064 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4065 PropertyValuesHolder.ofFloat("alpha", 1f),
4066 PropertyValuesHolder.ofFloat("scaleX", 1f),
4067 PropertyValuesHolder.ofFloat("scaleY", 1f));
4068 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4069 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4070 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4071 return bounceAnim;
4072 }
4073
4074 public boolean useVerticalBarLayout() {
4075 return LauncherAppState.getInstance().getDynamicGrid().
4076 getDeviceProfile().isVerticalBarLayout();
4077 }
4078
4079 protected Rect getSearchBarBounds() {
4080 return LauncherAppState.getInstance().getDynamicGrid().
4081 getDeviceProfile().getSearchBarBounds();
4082 }
4083
4084 public void bindSearchablesChanged() {
4085 if (mSearchDropTargetBar == null) {
4086 return;
4087 }
4088 if (mQsb != null) {
4089 mSearchDropTargetBar.removeView(mQsb);
4090 mQsb = null;
4091 }
4092 getOrCreateQsbBar();
4093 }
4094
4095 /**
4096 * Add the icons for all apps.
4097 *
4098 * Implementation of the method from LauncherModel.Callbacks.
4099 */
4100 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4101 <<<<<<< GitAnalyzerPlus_ours
4102 if (mAppsView != null) {
4103 mAppsView.setApps(apps);
4104 }
4105 if (mAppsCustomizeContent != null) {
4106 mAppsCustomizeContent.onPackagesUpdated(
4107 LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
4108 ||||||| GitAnalyzerPlus_base
4109 * Refreshes the shortcuts shown on the workspace.
4110 *
4111 * Implementation of the method from LauncherModel.Callbacks.
4112 */
4113 public void startBinding() {
4114 setWorkspaceLoading(true);
4115
4116 // If we're starting binding all over again, clear any bind calls we'd postponed in
4117 // the past (see waitUntilResume) -- we don't need them since we're starting binding
4118 // from scratch again
4119 mBindOnResumeCallbacks.clear();
4120
4121 // Clear the workspace because it's going to be rebound
4122 mWorkspace.clearDropTargets();
4123 mWorkspace.removeAllWorkspaceScreens();
4124
4125 mWidgetsToAdvance.clear();
4126 if (mHotseat != null) {
4127 mHotseat.resetLayout();
4128 }
4129 }
4130
4131 @Override
4132 public void bindScreens(ArrayList<Long> orderedScreenIds) {
4133 bindAddScreens(orderedScreenIds);
4134
4135 // If there are no screens, we need to have an empty screen
4136 if (orderedScreenIds.size() == 0) {
4137 mWorkspace.addExtraEmptyScreen();
4138 }
4139
4140 // Create the custom content page (this call updates mDefaultScreen which calls
4141 // setCurrentPage() so ensure that all pages are added before calling this).
4142 if (hasCustomContentToLeft()) {
4143 mWorkspace.createCustomContentContainer();
4144 populateCustomContentContainer();
4145 }
4146 }
4147
4148 @Override
4149 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
4150 // Log to disk
4151 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
4152 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
4153 TextUtils.join(", ", orderedScreenIds), true);
4154 int count = orderedScreenIds.size();
4155 for (int i = 0; i < count; i++) {
4156 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
4157 }
4158 }
4159
4160 @Override
4161 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
4162 final long screenId, final int[] cell, final int spanX, final int spanY) {
4163 showWorkspace(true, new Runnable() {
4164
4165 @Override
4166 public void run() {
4167 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
4168 addPendingItem(info, container, screenId, cell, spanX, spanY);
4169 }
4170 });
4171 }
4172
4173 private boolean shouldShowWeightWatcher() {
4174 String spKey = LauncherAppState.getSharedPreferencesKey();
4175 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4176 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
4177
4178 return show;
4179 }
4180
4181 private void toggleShowWeightWatcher() {
4182 String spKey = LauncherAppState.getSharedPreferencesKey();
4183 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4184 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
4185
4186 show = !show;
4187
4188 SharedPreferences.Editor editor = sp.edit();
4189 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
4190 editor.commit();
4191
4192 if (mWeightWatcher != null) {
4193 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
4194 }
4195 }
4196
4197 public void bindAppsAdded(final ArrayList<Long> newScreens,
4198 final ArrayList<ItemInfo> addNotAnimated,
4199 final ArrayList<ItemInfo> addAnimated,
4200 final ArrayList<AppInfo> addedApps) {
4201 Runnable r = new Runnable() {
4202 public void run() {
4203 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
4204 }
4205 };
4206 if (waitUntilResume(r)) {
4207 return;
4208 }
4209
4210 // Add the new screens
4211 if (newScreens != null) {
4212 bindAddScreens(newScreens);
4213 }
4214
4215 // We add the items without animation on non-visible pages, and with
4216 // animations on the new page (which we will try and snap to).
4217 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
4218 bindItems(addNotAnimated, 0,
4219 addNotAnimated.size(), false);
4220 }
4221 if (addAnimated != null && !addAnimated.isEmpty()) {
4222 bindItems(addAnimated, 0,
4223 addAnimated.size(), true);
4224 }
4225
4226 // Remove the extra empty screen
4227 mWorkspace.removeExtraEmptyScreen(false, false);
4228
4229 if (!LauncherAppState.isDisableAllApps() &&
4230 addedApps != null && mAppsCustomizeContent != null) {
4231 mAppsCustomizeContent.addApps(addedApps);
4232 }
4233 }
4234
4235 /**
4236 * Bind the items start-end from the list.
4237 *
4238 * Implementation of the method from LauncherModel.Callbacks.
4239 */
4240 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
4241 final boolean forceAnimateIcons) {
4242 Runnable r = new Runnable() {
4243 public void run() {
4244 bindItems(shortcuts, start, end, forceAnimateIcons);
4245 }
4246 };
4247 if (waitUntilResume(r)) {
4248 return;
4249 }
4250
4251 // Get the list of added shortcuts and intersect them with the set of shortcuts here
4252 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
4253 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
4254 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
4255 Workspace workspace = mWorkspace;
4256 long newShortcutsScreenId = -1;
4257 for (int i = start; i < end; i++) {
4258 final ItemInfo item = shortcuts.get(i);
4259
4260 // Short circuit if we are loading dock items for a configuration which has no dock
4261 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
4262 mHotseat == null) {
4263 continue;
4264 }
4265
4266 switch (item.itemType) {
4267 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
4268 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
4269 ShortcutInfo info = (ShortcutInfo) item;
4270 View shortcut = createShortcut(info);
4271
4272 /*
4273 * TODO: FIX collision case
4274 */
4275 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
4276 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
4277 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
4278 View v = cl.getChildAt(item.cellX, item.cellY);
4279 Object tag = v.getTag();
4280 String desc = "Collision while binding workspace item: " + item
4281 + ". Collides with " + tag;
4282 if (LauncherAppState.isDogfoodBuild()) {
4283 throw (new RuntimeException(desc));
4284 } else {
4285 Log.d(TAG, desc);
4286 }
4287 }
4288 }
4289
4290 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
4291 item.cellY, 1, 1);
4292 if (animateIcons) {
4293 // Animate all the applications up now
4294 shortcut.setAlpha(0f);
4295 shortcut.setScaleX(0f);
4296 shortcut.setScaleY(0f);
4297 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
4298 newShortcutsScreenId = item.screenId;
4299 }
4300 break;
4301 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
4302 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
4303 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
4304 (FolderInfo) item, mIconCache);
4305 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
4306 item.cellY, 1, 1);
4307 break;
4308 default:
4309 throw new RuntimeException("Invalid Item Type");
4310 }
4311 }
4312
4313 if (animateIcons) {
4314 // Animate to the correct page
4315 if (newShortcutsScreenId > -1) {
4316 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
4317 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
4318 final Runnable startBounceAnimRunnable = new Runnable() {
4319 public void run() {
4320 anim.playTogether(bounceAnims);
4321 anim.start();
4322 }
4323 };
4324 if (newShortcutsScreenId != currentScreenId) {
4325 // We post the animation slightly delayed to prevent slowdowns
4326 // when we are loading right after we return to launcher.
4327 mWorkspace.postDelayed(new Runnable() {
4328 public void run() {
4329 if (mWorkspace != null) {
4330 mWorkspace.snapToPage(newScreenIndex);
4331 mWorkspace.postDelayed(startBounceAnimRunnable,
4332 NEW_APPS_ANIMATION_DELAY);
4333 }
4334 }
4335 }, NEW_APPS_PAGE_MOVE_DELAY);
4336 } else {
4337 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
4338 }
4339 }
4340 }
4341 workspace.requestLayout();
4342 }
4343
4344 /**
4345 * Implementation of the method from LauncherModel.Callbacks.
4346 */
4347 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
4348 Runnable r = new Runnable() {
4349 public void run() {
4350 bindFolders(folders);
4351 }
4352 };
4353 if (waitUntilResume(r)) {
4354 return;
4355 }
4356 sFolders.clear();
4357 sFolders.putAll(folders);
4358 }
4359
4360 /**
4361 * Add the views for a widget to the workspace.
4362 *
4363 * Implementation of the method from LauncherModel.Callbacks.
4364 */
4365 public void bindAppWidget(final LauncherAppWidgetInfo item) {
4366 Runnable r = new Runnable() {
4367 public void run() {
4368 bindAppWidget(item);
4369 }
4370 };
4371 if (waitUntilResume(r)) {
4372 return;
4373 }
4374
4375 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
4376 if (DEBUG_WIDGETS) {
4377 Log.d(TAG, "bindAppWidget: " + item);
4378 }
4379 final Workspace workspace = mWorkspace;
4380
4381 LauncherAppWidgetProviderInfo appWidgetInfo =
4382 LauncherModel.getProviderInfo(this, item.providerName);
4383
4384 if (!mIsSafeModeEnabled
4385 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
4386 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
4387 if (appWidgetInfo == null) {
4388 if (DEBUG_WIDGETS) {
4389 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4390 + " belongs to component " + item.providerName
4391 + ", as the povider is null");
4392 }
4393 LauncherModel.deleteItemFromDatabase(this, item);
4394 return;
4395 }
4396 // Note: This assumes that the id remap broadcast is received before this step.
4397 // If that is not the case, the id remap will be ignored and user may see the
4398 // click to setup view.
4399 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
4400 pendingInfo.spanX = item.spanX;
4401 pendingInfo.spanY = item.spanY;
4402 pendingInfo.minSpanX = item.minSpanX;
4403 pendingInfo.minSpanY = item.minSpanY;
4404 Bundle options =
4405 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
4406
4407 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
4408 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
4409 newWidgetId, appWidgetInfo, options);
4410
4411 // TODO consider showing a permission dialog when the widget is clicked.
4412 if (!success) {
4413 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
4414 if (DEBUG_WIDGETS) {
4415 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4416 + " belongs to component " + item.providerName
4417 + ", as the launcher is unable to bing a new widget id");
4418 }
4419 LauncherModel.deleteItemFromDatabase(this, item);
4420 return;
4421 }
4422
4423 item.appWidgetId = newWidgetId;
4424
4425 // If the widget has a configure activity, it is still needs to set it up, otherwise
4426 // the widget is ready to go.
4427 item.restoreStatus = (appWidgetInfo.configure == null)
4428 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4429 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4430
4431 LauncherModel.updateItemInDatabase(this, item);
4432 }
4433
4434 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4435 final int appWidgetId = item.appWidgetId;
4436 if (DEBUG_WIDGETS) {
4437 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
4438 + appWidgetInfo.provider);
4439 }
4440
4441 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4442 } else {
4443 appWidgetInfo = null;
4444 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
4445 mIsSafeModeEnabled);
4446 view.updateIcon(mIconCache);
4447 item.hostView = view;
4448 item.hostView.updateAppWidget(null);
4449 item.hostView.setOnClickListener(this);
4450 }
4451
4452 item.hostView.setTag(item);
4453 item.onBindAppWidget(this);
4454
4455 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4456 item.cellY, item.spanX, item.spanY, false);
4457 if (!item.isCustomWidget()) {
4458 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4459 }
4460
4461 workspace.requestLayout();
4462
4463 if (DEBUG_WIDGETS) {
4464 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4465 + (SystemClock.uptimeMillis()-start) + "ms");
4466 }
4467 }
4468
4469 /**
4470 * Restores a pending widget.
4471 *
4472 * @param appWidgetId The app widget id
4473 * @param cellInfo The position on screen where to create the widget.
4474 */
4475 private void completeRestoreAppWidget(final int appWidgetId) {
4476 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4477 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4478 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4479 return;
4480 }
4481
4482 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4483 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4484
4485 mWorkspace.reinflateWidgetsIfNecessary();
4486 LauncherModel.updateItemInDatabase(this, info);
4487 }
4488
4489 public void onPageBoundSynchronously(int page) {
4490 mSynchronouslyBoundPages.add(page);
4491 }
4492
4493 /**
4494 * Callback saying that there aren't any more items to bind.
4495 *
4496 * Implementation of the method from LauncherModel.Callbacks.
4497 */
4498 public void finishBindingItems(final boolean upgradePath) {
4499 Runnable r = new Runnable() {
4500 public void run() {
4501 finishBindingItems(upgradePath);
4502 }
4503 };
4504 if (waitUntilResume(r)) {
4505 return;
4506 }
4507 if (mSavedState != null) {
4508 if (!mWorkspace.hasFocus()) {
4509 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4510 }
4511 mSavedState = null;
4512 }
4513
4514 mWorkspace.restoreInstanceStateForRemainingPages();
4515
4516 setWorkspaceLoading(false);
4517 sendLoadingCompleteBroadcastIfNecessary();
4518
4519 // If we received the result of any pending adds while the loader was running (e.g. the
4520 // widget configuration forced an orientation change), process them now.
4521 if (sPendingAddItem != null) {
4522 final long screenId = completeAdd(sPendingAddItem);
4523
4524 // TODO: this moves the user to the page where the pending item was added. Ideally,
4525 // the screen would be guaranteed to exist after bind, and the page would be set through
4526 // the workspace restore process.
4527 mWorkspace.post(new Runnable() {
4528 @Override
4529 public void run() {
4530 mWorkspace.snapToScreenId(screenId);
4531 }
4532 });
4533 sPendingAddItem = null;
4534 }
4535
4536 if (upgradePath) {
4537 mWorkspace.getUniqueComponents(true, null);
4538 mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
4539 }
4540 PackageInstallerCompat.getInstance(this).onFinishBind();
4541
4542 if (mLauncherCallbacks != null) {
4543 mLauncherCallbacks.finishBindingItems(upgradePath);
4544 }
4545 }
4546
4547 private void sendLoadingCompleteBroadcastIfNecessary() {
4548 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4549 String permission =
4550 getResources().getString(R.string.receive_first_load_broadcast_permission);
4551 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4552 sendBroadcast(intent, permission);
4553 SharedPreferences.Editor editor = mSharedPrefs.edit();
4554 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4555 editor.apply();
4556 }
4557 }
4558
4559 public boolean isAllAppsButtonRank(int rank) {
4560 if (mHotseat != null) {
4561 return mHotseat.isAllAppsButtonRank(rank);
4562 }
4563 return false;
4564 }
4565
4566 private boolean canRunNewAppsAnimation() {
4567 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4568 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4569 }
4570
4571 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4572 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4573 PropertyValuesHolder.ofFloat("alpha", 1f),
4574 PropertyValuesHolder.ofFloat("scaleX", 1f),
4575 PropertyValuesHolder.ofFloat("scaleY", 1f));
4576 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4577 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4578 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4579 return bounceAnim;
4580 }
4581
4582 public boolean useVerticalBarLayout() {
4583 return LauncherAppState.getInstance().getDynamicGrid().
4584 getDeviceProfile().isVerticalBarLayout();
4585 }
4586
4587 protected Rect getSearchBarBounds() {
4588 return LauncherAppState.getInstance().getDynamicGrid().
4589 getDeviceProfile().getSearchBarBounds();
4590 }
4591
4592 public void bindSearchablesChanged() {
4593 if (mSearchDropTargetBar == null) {
4594 return;
4595 }
4596 if (mQsb != null) {
4597 mSearchDropTargetBar.removeView(mQsb);
4598 mQsb = null;
4599 }
4600 mSearchDropTargetBar.setQsbSearchBar(getQsbBar());
4601 }
4602
4603 /**
4604 * Add the icons for all apps.
4605 *
4606 * Implementation of the method from LauncherModel.Callbacks.
4607 */
4608 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4609 if (LauncherAppState.isDisableAllApps()) {
4610 if (mIntentsOnWorkspaceFromUpgradePath != null) {
4611 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
4612 getHotseat().addAllAppsFolder(mIconCache, apps,
4613 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
4614 }
4615 mIntentsOnWorkspaceFromUpgradePath = null;
4616 }
4617 if (mAppsCustomizeContent != null) {
4618 mAppsCustomizeContent.onPackagesUpdated(
4619 LauncherModel.getSortedWidgetsAndShortcuts(this));
4620 }
4621 } else {
4622 if (mAppsCustomizeContent != null) {
4623 mAppsCustomizeContent.setApps(apps);
4624 mAppsCustomizeContent.onPackagesUpdated(
4625 LauncherModel.getSortedWidgetsAndShortcuts(this));
4626 }
4627 =======
4628 if (LauncherAppState.isDisableAllApps()) {
4629 if (mIntentsOnWorkspaceFromUpgradePath != null) {
4630 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
4631 getHotseat().addAllAppsFolder(mIconCache, apps,
4632 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
4633 }
4634 mIntentsOnWorkspaceFromUpgradePath = null;
4635 }
4636 if (mAppsCustomizeContent != null) {
4637 mAppsCustomizeContent.onPackagesUpdated(
4638 LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
4639 }
4640 } else {
4641 if (mAppsCustomizeContent != null) {
4642 mAppsCustomizeContent.setApps(apps);
4643 mAppsCustomizeContent.onPackagesUpdated(
4644 LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
4645 }
4646 >>>>>>> GitAnalyzerPlus_theirs
4647 }
4648 if (mLauncherCallbacks != null) {
4649 mLauncherCallbacks.bindAllApplications(apps);
4650 }
4651 }
4652
4653 /**
4654 * A package was updated.
4655 *
4656 * Implementation of the method from LauncherModel.Callbacks.
4657 */
4658 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4659 Runnable r = new Runnable() {
4660 public void run() {
4661 bindAppsUpdated(apps);
4662 }
4663 };
4664 if (waitUntilResume(r)) {
4665 return;
4666 }
4667
4668 if (mAppsView != null) {
4669 mAppsView.updateApps(apps);
4670 }
4671 }
4672
4673 @Override
4674 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4675 Runnable r = new Runnable() {
4676 public void run() {
4677 bindWidgetsRestored(widgets);
4678 }
4679 };
4680 if (waitUntilResume(r)) {
4681 return;
4682 }
4683 mWorkspace.widgetsRestored(widgets);
4684 }
4685
4686 /**
4687 * Some shortcuts were updated in the background.
4688 *
4689 * Implementation of the method from LauncherModel.Callbacks.
4690 */
4691 @Override
4692 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4693 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4694 Runnable r = new Runnable() {
4695 public void run() {
4696 bindShortcutsChanged(updated, removed, user);
4697 }
4698 };
4699 if (waitUntilResume(r)) {
4700 return;
4701 }
4702
4703 if (!updated.isEmpty()) {
4704 mWorkspace.updateShortcuts(updated);
4705 }
4706
4707 if (!removed.isEmpty()) {
4708 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4709 for (ShortcutInfo si : removed) {
4710 removedComponents.add(si.getTargetComponent());
4711 }
4712 mWorkspace.removeItemsByComponentName(removedComponents, user);
4713 // Notify the drag controller
4714 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4715 }
4716 }
4717
4718 /**
4719 * Update the state of a package, typically related to install state.
4720 *
4721 * Implementation of the method from LauncherModel.Callbacks.
4722 */
4723 @Override
4724 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4725 if (mWorkspace != null) {
4726 mWorkspace.updatePackageState(installInfo);
4727 }
4728 }
4729
4730 /**
4731 * Update the label and icon of all the icons in a package
4732 *
4733 * Implementation of the method from LauncherModel.Callbacks.
4734 */
4735 @Override
4736 public void updatePackageBadge(String packageName) {
4737 if (mWorkspace != null) {
4738 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4739 }
4740 }
4741
4742 /**
4743 * A package was uninstalled. We take both the super set of packageNames
4744 * in addition to specific applications to remove, the reason being that
4745 * this can be called when a package is updated as well. In that scenario,
4746 * we only remove specific components from the workspace, where as
4747 * package-removal should clear all items by package name.
4748 *
4749 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4750 * Implementation of the method from LauncherModel.Callbacks.
4751 */
4752 @Override
4753 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4754 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4755 Runnable r = new Runnable() {
4756 public void run() {
4757 bindComponentsRemoved(packageNames, appInfos, user, reason);
4758 }
4759 };
4760 if (waitUntilResume(r)) {
4761 return;
4762 }
4763
4764 if (reason == 0) {
4765 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4766 for (AppInfo info : appInfos) {
4767 removedComponents.add(info.componentName);
4768 }
4769 if (!packageNames.isEmpty()) {
4770 mWorkspace.removeItemsByPackageName(packageNames, user);
4771 }
4772 if (!removedComponents.isEmpty()) {
4773 mWorkspace.removeItemsByComponentName(removedComponents, user);
4774 }
4775 // Notify the drag controller
4776 mDragController.onAppsRemoved(packageNames, removedComponents);
4777
4778 } else {
4779 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4780 }
4781
4782 // Update AllApps
4783 if (mAppsView != null) {
4784 mAppsView.removeApps(appInfos);
4785 }
4786 }
4787
4788 /**
4789 * A number of packages were updated.
4790 */
4791 private ArrayList<Object> mWidgetsAndShortcuts;
4792 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4793 public void run() {
4794 bindPackagesUpdated(mWidgetsAndShortcuts);
4795 mWidgetsAndShortcuts = null;
4796 }
4797 };
4798 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4799 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4800 mWidgetsAndShortcuts = widgetsAndShortcuts;
4801 return;
4802 }
4803
4804 // Update the widgets pane
4805 if (mAppsCustomizeContent != null) {
4806 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
4807 }
4808 }
4809
4810 private int mapConfigurationOriActivityInfoOri(int configOri) {
4811 final Display d = getWindowManager().getDefaultDisplay();
4812 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4813 switch (d.getRotation()) {
4814 case Surface.ROTATION_0:
4815 case Surface.ROTATION_180:
4816 // We are currently in the same basic orientation as the natural orientation
4817 naturalOri = configOri;
4818 break;
4819 case Surface.ROTATION_90:
4820 case Surface.ROTATION_270:
4821 // We are currently in the other basic orientation to the natural orientation
4822 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4823 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4824 break;
4825 }
4826
4827 int[] oriMap = {
4828 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4829 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4830 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4831 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4832 };
4833 // Since the map starts at portrait, we need to offset if this device's natural orientation
4834 // is landscape.
4835 int indexOffset = 0;
4836 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4837 indexOffset = 1;
4838 }
4839 return oriMap[(d.getRotation() + indexOffset) % 4];
4840 }
4841
4842 public void lockScreenOrientation() {
4843 if (Utilities.isRotationEnabled(this)) {
4844 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4845 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4846 .getConfiguration().orientation));
4847 } else {
4848 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4849 }
4850 }
4851 }
4852 public void unlockScreenOrientation(boolean immediate) {
4853 if (Utilities.isRotationEnabled(this)) {
4854 if (immediate) {
4855 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4856 } else {
4857 mHandler.postDelayed(new Runnable() {
4858 public void run() {
4859 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4860 }
4861 }, mRestoreScreenOrientationDelay);
4862 }
4863 }
4864 }
4865
4866 protected boolean isLauncherPreinstalled() {
4867 if (mLauncherCallbacks != null) {
4868 return mLauncherCallbacks.isLauncherPreinstalled();
4869 }
4870 PackageManager pm = getPackageManager();
4871 try {
4872 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4873 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4874 return true;
4875 } else {
4876 return false;
4877 }
4878 } catch (NameNotFoundException e) {
4879 e.printStackTrace();
4880 return false;
4881 }
4882 }
4883
4884 /**
4885 * This method indicates whether or not we should suggest default wallpaper dimensions
4886 * when our wallpaper cropper was not yet used to set a wallpaper.
4887 */
4888 protected boolean overrideWallpaperDimensions() {
4889 if (mLauncherCallbacks != null) {
4890 return mLauncherCallbacks.overrideWallpaperDimensions();
4891 }
4892 return true;
4893 }
4894
4895 /**
4896 * To be overridden by subclasses to indicate that there is an activity to launch
4897 * before showing the standard launcher experience.
4898 */
4899 protected boolean hasFirstRunActivity() {
4900 if (mLauncherCallbacks != null) {
4901 return mLauncherCallbacks.hasFirstRunActivity();
4902 }
4903 return false;
4904 }
4905
4906 /**
4907 * To be overridden by subclasses to launch any first run activity
4908 */
4909 protected Intent getFirstRunActivity() {
4910 if (mLauncherCallbacks != null) {
4911 return mLauncherCallbacks.getFirstRunActivity();
4912 }
4913 return null;
4914 }
4915
4916 private boolean shouldRunFirstRunActivity() {
4917 return !ActivityManager.isRunningInTestHarness() &&
4918 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4919 }
4920
4921 protected boolean hasRunFirstRunActivity() {
4922 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4923 }
4924
4925 public boolean showFirstRunActivity() {
4926 if (shouldRunFirstRunActivity() &&
4927 hasFirstRunActivity()) {
4928 Intent firstRunIntent = getFirstRunActivity();
4929 if (firstRunIntent != null) {
4930 startActivity(firstRunIntent);
4931 markFirstRunActivityShown();
4932 return true;
4933 }
4934 }
4935 return false;
4936 }
4937
4938 private void markFirstRunActivityShown() {
4939 SharedPreferences.Editor editor = mSharedPrefs.edit();
4940 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4941 editor.apply();
4942 }
4943
4944 /**
4945 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4946 * screen that must be displayed and dismissed.
4947 */
4948 protected boolean hasDismissableIntroScreen() {
4949 if (mLauncherCallbacks != null) {
4950 return mLauncherCallbacks.hasDismissableIntroScreen();
4951 }
4952 return false;
4953 }
4954
4955 /**
4956 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4957 */
4958 protected View getIntroScreen() {
4959 if (mLauncherCallbacks != null) {
4960 return mLauncherCallbacks.getIntroScreen();
4961 }
4962 return null;
4963 }
4964
4965 /**
4966 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4967 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4968 */
4969 private boolean shouldShowIntroScreen() {
4970 return hasDismissableIntroScreen() &&
4971 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4972 }
4973
4974 protected void showIntroScreen() {
4975 View introScreen = getIntroScreen();
4976 changeWallpaperVisiblity(false);
4977 if (introScreen != null) {
4978 mDragLayer.showOverlayView(introScreen);
4979 }
4980 if (mLauncherOverlayContainer != null) {
4981 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4982 }
4983 }
4984
4985 public void dismissIntroScreen() {
4986 markIntroScreenDismissed();
4987 if (showFirstRunActivity()) {
4988 // We delay hiding the intro view until the first run activity is showing. This
4989 // avoids a blip.
4990 mWorkspace.postDelayed(new Runnable() {
4991 @Override
4992 public void run() {
4993 mDragLayer.dismissOverlayView();
4994 if (mLauncherOverlayContainer != null) {
4995 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4996 }
4997 showFirstRunClings();
4998 }
4999 }, ACTIVITY_START_DELAY);
5000 } else {
5001 mDragLayer.dismissOverlayView();
5002 if (mLauncherOverlayContainer != null) {
5003 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
5004 }
5005 showFirstRunClings();
5006 }
5007 changeWallpaperVisiblity(true);
5008 }
5009
5010 private void markIntroScreenDismissed() {
5011 SharedPreferences.Editor editor = mSharedPrefs.edit();
5012 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
5013 editor.apply();
5014 }
5015
5016 private void showFirstRunClings() {
5017 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
5018 // on the device, then we always show the first run cling experience (or if there is no
5019 // launcher2). Otherwise, we prompt the user upon started for migration
5020 LauncherClings launcherClings = new LauncherClings(this);
5021 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
5022 if (mModel.canMigrateFromOldLauncherDb(this)) {
5023 launcherClings.showMigrationCling();
5024 } else {
5025 launcherClings.showLongPressCling(true);
5026 }
5027 }
5028 }
5029
5030 void showWorkspaceSearchAndHotseat() {
5031 if (mWorkspace != null) mWorkspace.setAlpha(1f);
5032 if (mHotseat != null) mHotseat.setAlpha(1f);
5033 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
5034 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
5035 }
5036
5037 void hideWorkspaceSearchAndHotseat() {
5038 if (mWorkspace != null) mWorkspace.setAlpha(0f);
5039 if (mHotseat != null) mHotseat.setAlpha(0f);
5040 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
5041 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
5042 }
5043
5044 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
5045 // Called from search suggestion, not supported in other profiles.
5046 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
5047 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
5048 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
5049 myUser);
5050 if (activityInfo == null) {
5051 return null;
5052 }
5053 return new AppInfo(this, activityInfo, myUser, mIconCache);
5054 }
5055
5056 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5057 Bitmap icon) {
5058 // Called from search suggestion, not supported in other profiles.
5059 return createShortcutDragInfo(shortcutIntent, caption, icon,
5060 UserHandleCompat.myUserHandle());
5061 }
5062
5063 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5064 Bitmap icon, UserHandleCompat user) {
5065 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
5066 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
5067 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
5068 }
5069
5070 protected void moveWorkspaceToDefaultScreen() {
5071 mWorkspace.moveToDefaultScreen(false);
5072 }
5073
5074 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
5075 dragView.setTag(dragInfo);
5076 mWorkspace.onExternalDragStartedWithItem(dragView);
5077 mWorkspace.beginExternalDragShared(dragView, source);
5078 }
5079
5080 @Override
5081 public void onPageSwitch(View newPage, int newPageIndex) {
5082 if (mLauncherCallbacks != null) {
5083 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
5084 }
5085 }
5086
5087 /**
5088 * Prints out out state for debugging.
5089 */
5090 public void dumpState() {
5091 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
5092 Log.d(TAG, "mSavedState=" + mSavedState);
5093 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
5094 Log.d(TAG, "mRestoring=" + mRestoring);
5095 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
5096 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
5097 Log.d(TAG, "sFolders.size=" + sFolders.size());
5098 mModel.dumpState();
5099
5100 if (mAppsCustomizeContent != null) {
5101 mAppsCustomizeContent.dumpState();
5102 }
5103 Log.d(TAG, "END launcher3 dump state");
5104 }
5105
5106 @Override
5107 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
5108 super.dump(prefix, fd, writer, args);
5109 synchronized (sDumpLogs) {
5110 writer.println(" ");
5111 writer.println("Debug logs: ");
5112 for (int i = 0; i < sDumpLogs.size(); i++) {
5113 writer.println(" " + sDumpLogs.get(i));
5114 }
5115 }
5116 if (mLauncherCallbacks != null) {
5117 mLauncherCallbacks.dump(prefix, fd, writer, args);
5118 }
5119 }
5120
5121 public static void dumpDebugLogsToConsole() {
5122 if (DEBUG_DUMP_LOG) {
5123 synchronized (sDumpLogs) {
5124 Log.d(TAG, "");
5125 Log.d(TAG, "*********************");
5126 Log.d(TAG, "Launcher debug logs: ");
5127 for (int i = 0; i < sDumpLogs.size(); i++) {
5128 Log.d(TAG, " " + sDumpLogs.get(i));
5129 }
5130 Log.d(TAG, "*********************");
5131 Log.d(TAG, "");
5132 }
5133 }
5134 }
5135
5136 public static void addDumpLog(String tag, String log, boolean debugLog) {
5137 addDumpLog(tag, log, null, debugLog);
5138 }
5139
5140 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
5141 if (debugLog) {
5142 if (e != null) {
5143 Log.d(tag, log, e);
5144 } else {
5145 Log.d(tag, log);
5146 }
5147 }
5148 if (DEBUG_DUMP_LOG) {
5149 sDateStamp.setTime(System.currentTimeMillis());
5150 synchronized (sDumpLogs) {
5151 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
5152 + (e == null ? "" : (", Exception: " + e)));
5153 }
5154 }
5155 }
5156
5157 public static CustomAppWidget getCustomAppWidget(String name) {
5158 return sCustomAppWidgets.get(name);
5159 }
5160
5161 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
5162 return sCustomAppWidgets;
5163 }
5164
5165 public void dumpLogsToLocalData() {
5166 if (DEBUG_DUMP_LOG) {
5167 new AsyncTask<Void, Void, Void>() {
5168 public Void doInBackground(Void ... args) {
5169 boolean success = false;
5170 sDateStamp.setTime(sRunStart);
5171 String FILENAME = sDateStamp.getMonth() + "-"
5172 + sDateStamp.getDay() + "_"
5173 + sDateStamp.getHours() + "-"
5174 + sDateStamp.getMinutes() + "_"
5175 + sDateStamp.getSeconds() + ".txt";
5176
5177 FileOutputStream fos = null;
5178 File outFile = null;
5179 try {
5180 outFile = new File(getFilesDir(), FILENAME);
5181 outFile.createNewFile();
5182 fos = new FileOutputStream(outFile);
5183 } catch (Exception e) {
5184 e.printStackTrace();
5185 }
5186 if (fos != null) {
5187 PrintWriter writer = new PrintWriter(fos);
5188
5189 writer.println(" ");
5190 writer.println("Debug logs: ");
5191 synchronized (sDumpLogs) {
5192 for (int i = 0; i < sDumpLogs.size(); i++) {
5193 writer.println(" " + sDumpLogs.get(i));
5194 }
5195 }
5196 writer.close();
5197 }
5198 try {
5199 if (fos != null) {
5200 fos.close();
5201 success = true;
5202 }
5203 } catch (IOException e) {
5204 e.printStackTrace();
5205 }
5206 return null;
5207 }
5208 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
5209 }
5210 }
5211 }
5212
5213 interface LauncherTransitionable {
5214 View getContent();
5215 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
5216 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
5217 void onLauncherTransitionStep(Launcher l, float t);
5218 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
5219 }
5220
5221 interface DebugIntents {
5222 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
5223 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
5224 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.annotation.TargetApi;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.appwidget.AppWidgetHostView;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentCallbacks2;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.SharedPreferences;
44 import android.content.pm.ActivityInfo;
45 import android.content.pm.ApplicationInfo;
46 import android.content.pm.PackageManager;
47 import android.content.pm.PackageManager.NameNotFoundException;
48 import android.content.res.Configuration;
49 import android.database.ContentObserver;
50 import android.database.sqlite.SQLiteDatabase;
51 import android.graphics.Bitmap;
52 import android.graphics.Canvas;
53 import android.graphics.Color;
54 import android.graphics.PorterDuff;
55 import android.graphics.Rect;
56 import android.graphics.drawable.Drawable;
57 import android.net.Uri;
58 import android.os.AsyncTask;
59 import android.os.Build;
60 import android.os.Bundle;
61 import android.os.Environment;
62 import android.os.Handler;
63 import android.os.Message;
64 import android.os.StrictMode;
65 import android.os.SystemClock;
66 import android.text.Selection;
67 import android.text.SpannableStringBuilder;
68 import android.text.TextUtils;
69 import android.text.method.TextKeyListener;
70 import android.util.Log;
71 import android.view.Display;
72 import android.view.Gravity;
73 import android.view.HapticFeedbackConstants;
74 import android.view.KeyEvent;
75 import android.view.LayoutInflater;
76 import android.view.Menu;
77 import android.view.MotionEvent;
78 import android.view.Surface;
79 import android.view.View;
80 import android.view.View.OnClickListener;
81 import android.view.View.OnLongClickListener;
82 import android.view.ViewGroup;
83 import android.view.ViewStub;
84 import android.view.ViewTreeObserver;
85 import android.view.Window;
86 import android.view.WindowManager;
87 import android.view.accessibility.AccessibilityEvent;
88 import android.view.inputmethod.InputMethodManager;
89 import android.widget.Advanceable;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.TextView;
93 import android.widget.Toast;
94
95 import com.android.launcher3.DropTarget.DragObject;
96 import com.android.launcher3.PagedView.PageSwitchListener;
97 import com.android.launcher3.compat.AppWidgetManagerCompat;
98 import com.android.launcher3.compat.LauncherActivityInfoCompat;
99 import com.android.launcher3.compat.LauncherAppsCompat;
100 import com.android.launcher3.compat.PackageInstallerCompat;
101 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
102 import com.android.launcher3.compat.UserHandleCompat;
103 import com.android.launcher3.compat.UserManagerCompat;
104
105 import java.io.DataInputStream;
106 import java.io.DataOutputStream;
107 import java.io.File;
108 import java.io.FileDescriptor;
109 import java.io.FileNotFoundException;
110 import java.io.FileOutputStream;
111 import java.io.IOException;
112 import java.io.PrintWriter;
113 import java.lang.reflect.InvocationTargetException;
114 import java.lang.reflect.Method;
115 import java.text.DateFormat;
116 import java.util.ArrayList;
117 import java.util.Collection;
118 import java.util.Date;
119 import java.util.HashMap;
120 import java.util.HashSet;
121 import java.util.List;
122 import java.util.concurrent.atomic.AtomicInteger;
123
124 /**
125 * Default launcher application.
126 */
127 public class Launcher extends Activity
128 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
129 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
130 LauncherStateTransitionAnimation.Callbacks {
131 static final String TAG = "Launcher";
132 static final boolean LOGD = false;
133
134 static final boolean PROFILE_STARTUP = false;
135 static final boolean DEBUG_WIDGETS = false;
136 static final boolean DEBUG_STRICT_MODE = false;
137 static final boolean DEBUG_RESUME_TIME = false;
138 static final boolean DEBUG_DUMP_LOG = false;
139
140 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
141
142 private static final int REQUEST_CREATE_SHORTCUT = 1;
143 private static final int REQUEST_CREATE_APPWIDGET = 5;
144 private static final int REQUEST_PICK_APPWIDGET = 9;
145 private static final int REQUEST_PICK_WALLPAPER = 10;
146
147 private static final int REQUEST_BIND_APPWIDGET = 11;
148 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
149
150 /**
151 * IntentStarter uses request codes starting with this. This must be greater than all activity
152 * request codes used internally.
153 */
154 protected static final int REQUEST_LAST = 100;
155
156 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
157
158 static final int SCREEN_COUNT = 5;
159
160 // To turn on these properties, type
161 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
162 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
163
164 // The Intent extra that defines whether to ignore the launch animation
165 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
166 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
167
168 // Type: int
169 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
170 // Type: int
171 private static final String RUNTIME_STATE = "launcher.state";
172 // Type: int
173 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
174 // Type: int
175 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
176 // Type: int
177 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
178 // Type: int
179 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
180 // Type: boolean
181 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
182 // Type: long
183 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
184 // Type: int
185 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
186 // Type: int
187 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
188 // Type: parcelable
189 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
190 // Type: parcelable
191 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
192 // Type: int[]
193 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
194
195 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
196 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
197
198 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
199 static final String ACTION_FIRST_LOAD_COMPLETE =
200 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
201
202 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
203 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
204
205 private static final String QSB_WIDGET_ID = "qsb_widget_id";
206 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
207
208 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
209
210 /** The different states that Launcher can be in. */
211
212
213 /** The different states that Launcher can be in. */
214 enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED,, };;
215 private State mState = State.WORKSPACE;
216 private AnimatorSet mStateAnimation;
217 private LauncherStateTransitionAnimation mStateTransitionAnimation;
218
219 private boolean mIsSafeModeEnabled;
220
221 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
222 LauncherOverlay mLauncherOverlay;
223 InsettableFrameLayout mLauncherOverlayContainer;
224
225 static final int APPWIDGET_HOST_ID = 1024;
226 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
227 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
228 private static final int ACTIVITY_START_DELAY = 1000;
229
230 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
231 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
232
233 // How long to wait before the new-shortcut animation automatically pans the workspace
234 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
235 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
236 private static int NEW_APPS_ANIMATION_DELAY = 500;
237
238 private final BroadcastReceiver mCloseSystemDialogsReceiver
239 = new CloseSystemDialogsIntentReceiver();
240 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
241
242 private LayoutInflater mInflater;
243
244 private Workspace mWorkspace;
245 private View mLauncherView;
246 private View mPageIndicators;
247 private DragLayer mDragLayer;
248 private DragController mDragController;
249 private View mWeightWatcher;
250
251 private AppWidgetManagerCompat mAppWidgetManager;
252 private LauncherAppWidgetHost mAppWidgetHost;
253
254 private ItemInfo mPendingAddInfo = new ItemInfo();
255 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
256 private int mPendingAddWidgetId = -1;
257
258 private int[] mTmpAddItemCellCoordinates = new int[2];
259
260 private FolderInfo mFolderInfo;
261
262 private Hotseat mHotseat;
263 private ViewGroup mOverviewPanel;
264
265 private View mAllAppsButton;
266
267 private SearchDropTargetBar mSearchDropTargetBar;
268 private AppsContainerView mAppsView;
269 private AppsCustomizeTabHost mAppsCustomizeTabHost;
270 private AppsCustomizePagedView mAppsCustomizeContent;
271 private boolean mAutoAdvanceRunning = false;
272 private AppWidgetHostView mQsb;
273
274 private Bundle mSavedState;
275 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
276 // scroll issues (because the workspace may not have been measured yet) and extra work.
277 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
278 private State mOnResumeState = State.NONE;
279
280 private SpannableStringBuilder mDefaultKeySsb = null;
281
282 private boolean mWorkspaceLoading = true;
283
284 private boolean mPaused = true;
285 private boolean mRestoring;
286 private boolean mWaitingForResult;
287 private boolean mOnResumeNeedsLoad;
288
289 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
290 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
291
292 private Bundle mSavedInstanceState;
293
294 private LauncherModel mModel;
295 private IconCache mIconCache;
296 private boolean mUserPresent = true;
297 private boolean mVisible = false;
298 private boolean mHasFocus = false;
299 private boolean mAttached = false;
300
301 private static LocaleConfiguration sLocaleConfiguration = null;
302
303 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
304
305 private View.OnTouchListener mHapticFeedbackTouchListener;
306
307 // Related to the auto-advancing of widgets
308 private final int ADVANCE_MSG = 1;
309 private final int mAdvanceInterval = 20000;
310 private final int mAdvanceStagger = 250;
311 private long mAutoAdvanceSentTime;
312 private long mAutoAdvanceTimeLeft = -1;
313 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
314 new HashMap<View, AppWidgetProviderInfo>();
315
316 // Determines how long to wait after a rotation before restoring the screen orientation to
317 // match the sensor state.
318 private final int mRestoreScreenOrientationDelay = 500;
319
320 private Drawable mWorkspaceBackgroundDrawable;
321
322 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
323 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
324
325 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
326 static Date sDateStamp = new Date();
327 static DateFormat sDateFormat =
328 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
329 static long sRunStart = System.currentTimeMillis();
330 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
331
332 // We only want to get the SharedPreferences once since it does an FS stat each time we get
333 // it from the context.
334 private SharedPreferences mSharedPrefs;
335
336 // Holds the page that we need to animate to, and the icon views that we need to animate up
337 // when we scroll to that page on resume.
338 private ImageView mFolderIconImageView;
339 private Bitmap mFolderIconBitmap;
340 private Canvas mFolderIconCanvas;
341 private Rect mRectForFolderAnimation = new Rect();
342
343 private BubbleTextView mWaitingForResume;
344
345 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
346 new HashMap<String, CustomAppWidget>();
347
348 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
349
350 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
351 private static Method sClipRevealMethod = null;
352 static {
353 Class<?> activityOptionsClass = ActivityOptions.class;
354 try {
355 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
356 View.class, int.class, int.class, int.class, int.class);
357 } catch (Exception e) {
358 // Earlier version
359 }
360 }
361 static {
362 if (ENABLE_CUSTOM_WIDGET_TEST) {
363 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
364 }
365 }
366
367 private Runnable mBuildLayersRunnable = new Runnable() {
368 public void run() {
369 if (mWorkspace != null) {
370 mWorkspace.buildPageHardwareLayers();
371 }
372 }
373 };
374
375 private static PendingAddArguments sPendingAddItem;
376
377 private static class PendingAddArguments {
378 int requestCode;
379 Intent intent;
380 long container;
381 long screenId;
382 int cellX;
383 int cellY;
384 int appWidgetId;
385 }
386
387 private Stats mStats;
388
389 FocusIndicatorView mFocusHandler;
390
391 @Override
392 protected void onCreate(Bundle savedInstanceState) {
393 if (DEBUG_STRICT_MODE) {
394 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
395 .detectDiskReads()
396 .detectDiskWrites()
397 .detectNetwork() // or .detectAll() for all detectable problems
398 .penaltyLog()
399 .build());
400 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
401 .detectLeakedSqlLiteObjects()
402 .detectLeakedClosableObjects()
403 .penaltyLog()
404 .penaltyDeath()
405 .build());
406 }
407
408 if (mLauncherCallbacks != null) {
409 mLauncherCallbacks.preOnCreate();
410 }
411
412 super.onCreate(savedInstanceState);
413
414 LauncherAppState.setApplicationContext(getApplicationContext());
415 LauncherAppState app = LauncherAppState.getInstance();
416 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
417
418 // Lazy-initialize the dynamic grid
419 DeviceProfile grid = app.initDynamicGrid(this);
420
421 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
422 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
423 Context.MODE_PRIVATE);
424 mIsSafeModeEnabled = getPackageManager().isSafeMode();
425 mModel = app.setLauncher(this);
426 mIconCache = app.getIconCache();
427 mIconCache.flushInvalidIcons(grid);
428 mDragController = new DragController(this);
429 mInflater = getLayoutInflater();
430 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
431
432 mStats = new Stats(this);
433
434 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
435
436 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
437 mAppWidgetHost.startListening();
438
439 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
440 // this also ensures that any synchronous binding below doesn't re-trigger another
441 // LauncherModel load.
442 mPaused = false;
443
444 if (PROFILE_STARTUP) {
445 android.os.Debug.startMethodTracing(
446 Environment.getExternalStorageDirectory() + "/launcher");
447 }
448
449 checkForLocaleChange();
450 setContentView(R.layout.launcher);
451
452 setupViews();
453 grid.layout(this);
454
455 registerContentObservers();
456
457 lockAllApps();
458
459 mSavedState = savedInstanceState;
460 restoreState(mSavedState);
461
462 if (PROFILE_STARTUP) {
463 android.os.Debug.stopMethodTracing();
464 }
465
466 if (!mRestoring) {
467 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
468 // If the user leaves launcher, then we should just load items asynchronously when
469 // they return.
470 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
471 } else {
472 // We only load the page synchronously if the user rotates (or triggers a
473 // configuration change) while launcher is in the foreground
474 mModel.startLoader(true, mWorkspace.getRestorePage());
475 }
476 }
477
478 // For handling default keys
479 mDefaultKeySsb = new SpannableStringBuilder();
480 Selection.setSelection(mDefaultKeySsb, 0);
481
482 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
483 registerReceiver(mCloseSystemDialogsReceiver, filter);
484
485 // On large interfaces, we want the screen to auto-rotate based on the current orientation
486 unlockScreenOrientation(true);
487
488 if (mLauncherCallbacks != null) {
489 mLauncherCallbacks.onCreate(savedInstanceState);
490 if (mLauncherCallbacks.hasLauncherOverlay()) {
491 ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
492 mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
493 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
494 mLauncherOverlayContainer, mLauncherOverlayCallbacks);
495 mWorkspace.setLauncherOverlay(mLauncherOverlay);
496 }
497 }
498
499 if (shouldShowIntroScreen()) {
500 showIntroScreen();
501 } else {
502 showFirstRunActivity();
503 showFirstRunClings();
504 }
505 }
506
507 private LauncherCallbacks mLauncherCallbacks;
508
509 public void onPostCreate(Bundle savedInstanceState) {
510 super.onPostCreate(savedInstanceState);
511 if (mLauncherCallbacks != null) {
512 mLauncherCallbacks.onPostCreate(savedInstanceState);
513 }
514 }
515
516 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
517 mLauncherCallbacks = callbacks;
518 return true;
519 }
520
521 @Override
522 public void onLauncherProviderChange() {
523 if (mLauncherCallbacks != null) {
524 mLauncherCallbacks.onLauncherProviderChange();
525 }
526 }
527
528 /** To be overridden by subclasses to hint to Launcher that we have custom content */
529 protected boolean hasCustomContentToLeft() {
530 if (mLauncherCallbacks != null) {
531 return mLauncherCallbacks.hasCustomContentToLeft();
532 }
533 return false;
534 }
535
536 /**
537 * To be overridden by subclasses to populate the custom content container and call
538 * {@link #addToCustomContentPage}. This will only be invoked if
539 * {@link #hasCustomContentToLeft()} is {@code true}.
540 */
541 protected void populateCustomContentContainer() {
542 if (mLauncherCallbacks != null) {
543 mLauncherCallbacks.populateCustomContentContainer();
544 }
545 }
546
547 /**
548 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
549 * ensure the custom content page is added or removed if necessary.
550 */
551 protected void invalidateHasCustomContentToLeft() {
552 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
553 // Not bound yet, wait for bindScreens to be called.
554 return;
555 }
556
557 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
558 // Create the custom content page and call the subclass to populate it.
559 mWorkspace.createCustomContentContainer();
560 populateCustomContentContainer();
561 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
562 mWorkspace.removeCustomContentPage();
563 }
564 }
565
566 private void checkForLocaleChange() {
567 if (sLocaleConfiguration == null) {
568 new AsyncTask<Void, Void, LocaleConfiguration>() {
569 @Override
570 protected LocaleConfiguration doInBackground(Void... unused) {
571 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
572 readConfiguration(Launcher.this, localeConfiguration);
573 return localeConfiguration;
574 }
575
576 @Override
577 protected void onPostExecute(LocaleConfiguration result) {
578 sLocaleConfiguration = result;
579 checkForLocaleChange(); // recursive, but now with a locale configuration
580 }
581 }.execute();
582 return;
583 }
584
585 final Configuration configuration = getResources().getConfiguration();
586
587 final String previousLocale = sLocaleConfiguration.locale;
588 final String locale = configuration.locale.toString();
589
590 final int previousMcc = sLocaleConfiguration.mcc;
591 final int mcc = configuration.mcc;
592
593 final int previousMnc = sLocaleConfiguration.mnc;
594 final int mnc = configuration.mnc;
595
596 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
597
598 if (localeChanged) {
599 sLocaleConfiguration.locale = locale;
600 sLocaleConfiguration.mcc = mcc;
601 sLocaleConfiguration.mnc = mnc;
602
603 mIconCache.flush();
604
605 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
606 new AsyncTask<Void, Void, Void>() {
607 public Void doInBackground(Void ... args) {
608 writeConfiguration(Launcher.this, localeConfiguration);
609 return null;
610 }
611 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
612 }
613 }
614
615 private static class LocaleConfiguration {
616 public String locale;
617 public int mcc = -1;
618 public int mnc = -1;
619 }
620
621 private static void readConfiguration(Context context, LocaleConfiguration configuration) {
622 DataInputStream in = null;
623 try {
624 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
625 configuration.locale = in.readUTF();
626 configuration.mcc = in.readInt();
627 configuration.mnc = in.readInt();
628 } catch (FileNotFoundException e) {
629 // Ignore
630 } catch (IOException e) {
631 // Ignore
632 } finally {
633 if (in != null) {
634 try {
635 in.close();
636 } catch (IOException e) {
637 // Ignore
638 }
639 }
640 }
641 }
642
643 private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
644 DataOutputStream out = null;
645 try {
646 out = new DataOutputStream(context.openFileOutput(
647 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
648 out.writeUTF(configuration.locale);
649 out.writeInt(configuration.mcc);
650 out.writeInt(configuration.mnc);
651 out.flush();
652 } catch (FileNotFoundException e) {
653 // Ignore
654 } catch (IOException e) {
655 //noinspection ResultOfMethodCallIgnored
656 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
657 } finally {
658 if (out != null) {
659 try {
660 out.close();
661 } catch (IOException e) {
662 // Ignore
663 }
664 }
665 }
666 }
667
668 public Stats getStats() {
669 return mStats;
670 }
671
672 public LayoutInflater getInflater() {
673 return mInflater;
674 }
675
676 boolean isDraggingEnabled() {
677 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
678 // that is subsequently removed from the workspace in startBinding().
679 return !mModel.isLoadingWorkspace();
680 }
681
682 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
683 public static int generateViewId() {
684 if (Build.VERSION.SDK_INT >= 17) {
685 return View.generateViewId();
686 } else {
687 // View.generateViewId() is not available. The following fallback logic is a copy
688 // of its implementation.
689 for (;;) {
690 final int result = sNextGeneratedId.get();
691 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
692 int newValue = result + 1;
693 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
694 if (sNextGeneratedId.compareAndSet(result, newValue)) {
695 return result;
696 }
697 }
698 }
699 }
700
701 public int getViewIdForItem(ItemInfo info) {
702 // This cast is safe given the > 2B range for int.
703 int itemId = (int) info.id;
704 if (mItemIdToViewId.containsKey(itemId)) {
705 return mItemIdToViewId.get(itemId);
706 }
707 int viewId = generateViewId();
708 mItemIdToViewId.put(itemId, viewId);
709 return viewId;
710 }
711
712 /**
713 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
714 * a configuration step, this allows the proper animations to run after other transitions.
715 */
716 private long completeAdd(PendingAddArguments args) {
717 long screenId = args.screenId;
718 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
719 // When the screen id represents an actual screen (as opposed to a rank) we make sure
720 // that the drop page actually exists.
721 screenId = ensurePendingDropLayoutExists(args.screenId);
722 }
723
724 switch (args.requestCode) {
725 case REQUEST_CREATE_SHORTCUT:
726 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
727 args.cellY);
728 break;
729 case REQUEST_CREATE_APPWIDGET:
730 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
731 break;
732 case REQUEST_RECONFIGURE_APPWIDGET:
733 completeRestoreAppWidget(args.appWidgetId);
734 break;
735 }
736 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
737 // if you turned the screen off and then back while in All Apps, Launcher would not
738 // return to the workspace. Clearing mAddInfo.container here fixes this issue
739 resetAddInfo();
740 return screenId;
741 }
742
743 private void handleActivityResult(
744 final int requestCode, final int resultCode, final Intent data) {
745 // Reset the startActivity waiting flag
746 setWaitingForResult(false);
747 final int pendingAddWidgetId = mPendingAddWidgetId;
748 mPendingAddWidgetId = -1;
749
750 Runnable exitSpringLoaded = new Runnable() {
751 @Override
752 public void run() {
753 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
754 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
755 }
756 };
757
758 if (requestCode == REQUEST_BIND_APPWIDGET) {
759 final int appWidgetId = data != null ?
760 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
761 if (resultCode == RESULT_CANCELED) {
762 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
763 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
764 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
765 } else if (resultCode == RESULT_OK) {
766 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
767 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
768 }
769 return;
770 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
771 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
772 mWorkspace.exitOverviewMode(false);
773 }
774 return;
775 }
776
777 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
778 requestCode == REQUEST_CREATE_APPWIDGET);
779
780 final boolean workspaceLocked = isWorkspaceLocked();
781 // We have special handling for widgets
782 if (isWidgetDrop) {
783 final int appWidgetId;
784 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
785 : -1;
786 if (widgetId < 0) {
787 appWidgetId = pendingAddWidgetId;
788 } else {
789 appWidgetId = widgetId;
790 }
791
792 final int result;
793 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
794 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
795 "returned from the widget configuration activity.");
796 result = RESULT_CANCELED;
797 completeTwoStageWidgetDrop(result, appWidgetId);
798 final Runnable onComplete = new Runnable() {
799 @Override
800 public void run() {
801 exitSpringLoadedDragModeDelayed(false, 0, null);
802 }
803 };
804 if (workspaceLocked) {
805 // No need to remove the empty screen if we're mid-binding, as the
806 // the bind will not add the empty screen.
807 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
808 } else {
809 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
810 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
811 }
812 } else {
813 if (!workspaceLocked) {
814 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
815 // When the screen id represents an actual screen (as opposed to a rank)
816 // we make sure that the drop page actually exists.
817 mPendingAddInfo.screenId =
818 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
819 }
820 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
821
822 dropLayout.setDropPending(true);
823 final Runnable onComplete = new Runnable() {
824 @Override
825 public void run() {
826 completeTwoStageWidgetDrop(resultCode, appWidgetId);
827 dropLayout.setDropPending(false);
828 }
829 };
830 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
831 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
832 } else {
833 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
834 mPendingAddInfo);
835 sPendingAddItem = args;
836 }
837 }
838 return;
839 }
840
841 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
842 if (resultCode == RESULT_OK) {
843 // Update the widget view.
844 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
845 pendingAddWidgetId, mPendingAddInfo);
846 if (workspaceLocked) {
847 sPendingAddItem = args;
848 } else {
849 completeAdd(args);
850 }
851 }
852 // Leave the widget in the pending state if the user canceled the configure.
853 return;
854 }
855
856 // The pattern used here is that a user PICKs a specific application,
857 // which, depending on the target, might need to CREATE the actual target.
858
859 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
860 // launch over to the Music app to actually CREATE_SHORTCUT.
861 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
862 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
863 mPendingAddInfo);
864 if (isWorkspaceLocked()) {
865 sPendingAddItem = args;
866 } else {
867 completeAdd(args);
868 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
869 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
870 }
871 } else if (resultCode == RESULT_CANCELED) {
872 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
873 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
874 }
875 mDragLayer.clearAnimatedView();
876
877 }
878
879 @Override
880 protected void onActivityResult(
881 final int requestCode, final int resultCode, final Intent data) {
882 handleActivityResult(requestCode, resultCode, data);
883 if (mLauncherCallbacks != null) {
884 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
885 }
886 }
887
888 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
889 appWidgetId, ItemInfo info) {
890 PendingAddArguments args = new PendingAddArguments();
891 args.requestCode = requestCode;
892 args.intent = data;
893 args.container = info.container;
894 args.screenId = info.screenId;
895 args.cellX = info.cellX;
896 args.cellY = info.cellY;
897 args.appWidgetId = appWidgetId;
898 return args;
899 }
900
901 /**
902 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
903 *
904 * @param screenId the screen id to check
905 * @return the new screen, or screenId if it exists
906 */
907 private long ensurePendingDropLayoutExists(long screenId) {
908 CellLayout dropLayout =
909 (CellLayout) mWorkspace.getScreenWithId(screenId);
910 if (dropLayout == null) {
911 // it's possible that the add screen was removed because it was
912 // empty and a re-bind occurred
913 mWorkspace.addExtraEmptyScreen();
914 return mWorkspace.commitExtraEmptyScreen();
915 } else {
916 return screenId;
917 }
918 }
919
920 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
921 CellLayout cellLayout =
922 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
923 Runnable onCompleteRunnable = null;
924 int animationType = 0;
925
926 AppWidgetHostView boundWidget = null;
927 if (resultCode == RESULT_OK) {
928 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
929 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
930 mPendingAddWidgetInfo);
931 boundWidget = layout;
932 onCompleteRunnable = new Runnable() {
933 @Override
934 public void run() {
935 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
936 mPendingAddInfo.screenId, layout, null);
937 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
938 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
939 }
940 };
941 } else if (resultCode == RESULT_CANCELED) {
942 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
943 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
944 }
945 if (mDragLayer.getAnimatedView() != null) {
946 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
947 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
948 animationType, boundWidget, true);
949 } else if (onCompleteRunnable != null) {
950 // The animated view may be null in the case of a rotation during widget configuration
951 onCompleteRunnable.run();
952 }
953 }
954
955 @Override
956 protected void onStop() {
957 super.onStop();
958 FirstFrameAnimatorHelper.setIsVisible(false);
959
960 if (mLauncherCallbacks != null) {
961 mLauncherCallbacks.onStop();
962 }
963 }
964
965 @Override
966 protected void onStart() {
967 super.onStart();
968 FirstFrameAnimatorHelper.setIsVisible(true);
969
970 if (mLauncherCallbacks != null) {
971 mLauncherCallbacks.onStart();
972 }
973 }
974
975 @Override
976 protected void onResume() {
977 long startTime = 0;
978 if (DEBUG_RESUME_TIME) {
979 startTime = System.currentTimeMillis();
980 Log.v(TAG, "Launcher.onResume()");
981 }
982
983 if (mLauncherCallbacks != null) {
984 mLauncherCallbacks.preOnResume();
985 }
986
987 super.onResume();
988
989 // Restore the previous launcher state
990 if (mOnResumeState == State.WORKSPACE || mOnResumeState == State.NONE) {
991 showWorkspace(false);
992 } else if (mOnResumeState == State.APPS) {
993 showAppsView(false /* animated */, false /* resetListToTop */);
994 } else if (mOnResumeState == State.WIDGETS) {
995 showWidgetsView(false, false);
996 }
997 mOnResumeState = State.NONE;
998
999 // Background was set to gradient in onPause(), restore to black if in all apps.
1000 setWorkspaceBackground(mState == State.WORKSPACE);
1001
1002 mPaused = false;
1003 if (mRestoring || mOnResumeNeedsLoad) {
1004 setWorkspaceLoading(true);
1005 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1006 mRestoring = false;
1007 mOnResumeNeedsLoad = false;
1008 }
1009 if (mBindOnResumeCallbacks.size() > 0) {
1010 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1011 // execute them here
1012 long startTimeCallbacks = 0;
1013 if (DEBUG_RESUME_TIME) {
1014 startTimeCallbacks = System.currentTimeMillis();
1015 }
1016
1017 if (mAppsCustomizeContent != null) {
1018 mAppsCustomizeContent.setBulkBind(true);
1019 }
1020 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1021 mBindOnResumeCallbacks.get(i).run();
1022 }
1023 if (mAppsCustomizeContent != null) {
1024 mAppsCustomizeContent.setBulkBind(false);
1025 }
1026 mBindOnResumeCallbacks.clear();
1027 if (DEBUG_RESUME_TIME) {
1028 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1029 (System.currentTimeMillis() - startTimeCallbacks));
1030 }
1031 }
1032 if (mOnResumeCallbacks.size() > 0) {
1033 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1034 mOnResumeCallbacks.get(i).run();
1035 }
1036 mOnResumeCallbacks.clear();
1037 }
1038
1039 // Reset the pressed state of icons that were locked in the press state while activities
1040 // were launching
1041 if (mWaitingForResume != null) {
1042 // Resets the previous workspace icon press state
1043 mWaitingForResume.setStayPressed(false);
1044 }
1045
1046 // It is possible that widgets can receive updates while launcher is not in the foreground.
1047 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1048 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1049 // orientation.
1050 getWorkspace().reinflateWidgetsIfNecessary();
1051
1052 // Process any items that were added while Launcher was away.
1053 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1054
1055 if (DEBUG_RESUME_TIME) {
1056 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1057 }
1058
1059 if (mWorkspace.getCustomContentCallbacks() != null) {
1060 // If we are resuming and the custom content is the current page, we call onShow().
1061 // It is also poassible that onShow will instead be called slightly after first layout
1062 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1063 if (mWorkspace.isOnOrMovingToCustomContent()) {
1064 mWorkspace.getCustomContentCallbacks().onShow(true);
1065 }
1066 }
1067 mWorkspace.updateInteractionForState();
1068 mWorkspace.onResume();
1069
1070 PackageInstallerCompat.getInstance(this).onResume();
1071
1072 if (mLauncherCallbacks != null) {
1073 mLauncherCallbacks.onResume();
1074 }
1075 }
1076
1077 @Override
1078 protected void onPause() {
1079 // Ensure that items added to Launcher are queued until Launcher returns
1080 InstallShortcutReceiver.enableInstallQueue();
1081 PackageInstallerCompat.getInstance(this).onPause();
1082
1083 super.onPause();
1084 mPaused = true;
1085 mDragController.cancelDrag();
1086 mDragController.resetLastGestureUpTime();
1087
1088 // We call onHide() aggressively. The custom content callbacks should be able to
1089 // debounce excess onHide calls.
1090 if (mWorkspace.getCustomContentCallbacks() != null) {
1091 mWorkspace.getCustomContentCallbacks().onHide();
1092 }
1093
1094 if (mLauncherCallbacks != null) {
1095 mLauncherCallbacks.onPause();
1096 }
1097 }
1098
1099 public interface CustomContentCallbacks {
1100 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1101 // by a onResume or by scrolling otherwise.
1102 public void onShow(boolean fromResume);
1103
1104 // Custom content is completely hidden
1105 public void onHide();
1106
1107 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1108 public void onScrollProgressChanged(float progress);
1109
1110 // Indicates whether the user is allowed to scroll away from the custom content.
1111 boolean isScrollingAllowed();
1112 }
1113
1114 public interface LauncherOverlay {
1115
1116 /**
1117 * Touch interaction leading to overscroll has begun
1118 */
1119 public void onScrollInteractionBegin();
1120
1121 /**
1122 * Touch interaction related to overscroll has ended
1123 */
1124 public void onScrollInteractionEnd();
1125
1126 /**
1127 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1128 * screen (or in the case of RTL, the rightmost screen).
1129 */
1130 public void onScrollChange(int progress, boolean rtl);
1131
1132 /**
1133 * Screen has stopped scrolling
1134 */
1135 public void onScrollSettled();
1136
1137 /**
1138 * This method can be called by the Launcher in order to force the LauncherOverlay
1139 * to exit fully immersive mode.
1140 */
1141 public void forceExitFullImmersion();
1142 }
1143
1144 public interface LauncherOverlayCallbacks {
1145 /**
1146 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1147 * however it doesn't modify any state within the launcher.
1148 */
1149 public boolean canEnterFullImmersion();
1150
1151 /**
1152 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1153 * eg. by occupying the full screen and handling all touch events.
1154 *
1155 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1156 * case, Launcher will modify any necessary state and assumes the overlay is
1157 * handling all interaction. If false, the LauncherOverlay should cancel any
1158 *
1159 */
1160 public boolean enterFullImmersion();
1161
1162 /**
1163 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1164 * full control over UI and state.
1165 */
1166 public void exitFullImmersion();
1167 }
1168
1169 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1170
1171 @Override
1172 public boolean canEnterFullImmersion() {
1173 return mState == State.WORKSPACE;
1174 }
1175
1176 @Override
1177 public boolean enterFullImmersion() {
1178 if (mState == State.WORKSPACE) {
1179 // When fully immersed, disregard any touches which fall through.
1180 mDragLayer.setBlockTouch(true);
1181 return true;
1182 }
1183 return false;
1184 }
1185
1186 @Override
1187 public void exitFullImmersion() {
1188 mDragLayer.setBlockTouch(false);
1189 }
1190 }
1191
1192 protected boolean hasSettings() {
1193 if (mLauncherCallbacks != null) {
1194 return mLauncherCallbacks.hasSettings();
1195 }
1196 return false;
1197 }
1198
1199
1200 public void addToCustomContentPage(View customContent,
1201 CustomContentCallbacks callbacks, String description) {
1202 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1203 }
1204
1205 // The custom content needs to offset its content to account for the QSB
1206 public int getTopOffsetForCustomContent() {
1207 return mWorkspace.getPaddingTop();
1208 }
1209
1210 @Override
1211 public Object onRetainNonConfigurationInstance() {
1212 // Flag the loader to stop early before switching
1213 if (mModel.isCurrentCallbacks(this)) {
1214 mModel.stopLoader();
1215 }
1216 if (mAppsCustomizeContent != null) {
1217 mAppsCustomizeContent.surrender();
1218 }
1219 return Boolean.TRUE;
1220 }
1221
1222 // We can't hide the IME if it was forced open. So don't bother
1223 @Override
1224 public void onWindowFocusChanged(boolean hasFocus) {
1225 super.onWindowFocusChanged(hasFocus);
1226 mHasFocus = hasFocus;
1227
1228 if (mLauncherCallbacks != null) {
1229 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1230 }
1231 }
1232
1233 private boolean acceptFilter() {
1234 final InputMethodManager inputManager = (InputMethodManager)
1235 getSystemService(Context.INPUT_METHOD_SERVICE);
1236 return !inputManager.isFullscreenMode();
1237 }
1238
1239 @Override
1240 public boolean onKeyDown(int keyCode, KeyEvent event) {
1241 final int uniChar = event.getUnicodeChar();
1242 final boolean handled = super.onKeyDown(keyCode, event);
1243 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1244 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1245 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1246 keyCode, event);
1247 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1248 // something usable has been typed - start a search
1249 // the typed text will be retrieved and cleared by
1250 // showSearchDialog()
1251 // If there are multiple keystrokes before the search dialog takes focus,
1252 // onSearchRequested() will be called for every keystroke,
1253 // but it is idempotent, so it's fine.
1254 return onSearchRequested();
1255 }
1256 }
1257
1258 // Eat the long press event so the keyboard doesn't come up.
1259 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1260 return true;
1261 }
1262
1263 return handled;
1264 }
1265
1266 private String getTypedText() {
1267 return mDefaultKeySsb.toString();
1268 }
1269
1270 private void clearTypedText() {
1271 mDefaultKeySsb.clear();
1272 mDefaultKeySsb.clearSpans();
1273 Selection.setSelection(mDefaultKeySsb, 0);
1274 }
1275
1276 /**
1277 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1278 * State
1279 */
1280 private static State intToState(int stateOrdinal) {
1281 State state = State.WORKSPACE;
1282 final State[] stateValues = State.values();
1283 for (int i = 0; i < stateValues.length; i++) {
1284 if (stateValues[i].ordinal() == stateOrdinal) {
1285 state = stateValues[i];
1286 break;
1287 }
1288 }
1289 return state;
1290 }
1291
1292 /**
1293 * Restores the previous state, if it exists.
1294 *
1295 * @param savedState The previous state.
1296 */
1297 @SuppressWarnings("unchecked")
1298 private void restoreState(Bundle savedState) {
1299 if (savedState == null) {
1300 return;
1301 }
1302
1303 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1304 if (state == State.APPS || state == State.WIDGETS) {
1305 mOnResumeState = state;
1306 }
1307
1308 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1309 PagedView.INVALID_RESTORE_PAGE);
1310 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1311 mWorkspace.setRestorePage(currentScreen);
1312 }
1313
1314 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1315 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1316
1317 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1318 mPendingAddInfo.container = pendingAddContainer;
1319 mPendingAddInfo.screenId = pendingAddScreen;
1320 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1321 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1322 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1323 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1324 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1325 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1326 setWaitingForResult(true);
1327 mRestoring = true;
1328 }
1329
1330 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1331 if (renameFolder) {
1332 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1333 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1334 mRestoring = true;
1335 }
1336
1337 // Restore the AppsCustomize tab
1338 if (mAppsCustomizeTabHost != null) {
1339 String curTab = savedState.getString("apps_customize_currentTab");
1340 if (curTab != null) {
1341 mAppsCustomizeTabHost.setContentTypeImmediate(
1342 mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
1343 mAppsCustomizeContent.loadAssociatedPages(
1344 mAppsCustomizeContent.getCurrentPage());
1345 }
1346
1347 int currentIndex = savedState.getInt("apps_customize_currentIndex");
1348 mAppsCustomizeContent.restorePageForIndex(currentIndex);
1349 }
1350 mItemIdToViewId = (HashMap<Integer, Integer>)
1351 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1352 }
1353
1354 /**
1355 * Finds all the views we need and configure them properly.
1356 */
1357 private void setupViews() {
1358 final DragController dragController = mDragController;
1359
1360 mLauncherView = findViewById(R.id.launcher);
1361 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1362 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1363 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1364 mWorkspace.setPageSwitchListener(this);
1365 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1366
1367 mLauncherView.setSystemUiVisibility(
1368 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1369 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1370
1371 // Setup the drag layer
1372 mDragLayer.setup(this, dragController);
1373
1374 // Setup the hotseat
1375 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1376 if (mHotseat != null) {
1377 mHotseat.setup(this);
1378 mHotseat.setOnLongClickListener(this);
1379 }
1380
1381 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1382 View widgetButton = findViewById(R.id.widget_button);
1383 widgetButton.setOnClickListener(new OnClickListener() {
1384 @Override
1385 public void onClick(View arg0) {
1386 if (!mWorkspace.isSwitchingState()) {
1387 onClickAddWidgetButton(arg0);
1388 }
1389 }
1390 });
1391 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1392
1393 View wallpaperButton = findViewById(R.id.wallpaper_button);
1394 wallpaperButton.setOnClickListener(new OnClickListener() {
1395 @Override
1396 public void onClick(View arg0) {
1397 if (!mWorkspace.isSwitchingState()) {
1398 onClickWallpaperPicker(arg0);
1399 }
1400 }
1401 });
1402 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1403
1404 View settingsButton = findViewById(R.id.settings_button);
1405 if (hasSettings()) {
1406 settingsButton.setOnClickListener(new OnClickListener() {
1407 @Override
1408 public void onClick(View arg0) {
1409 if (!mWorkspace.isSwitchingState()) {
1410 onClickSettingsButton(arg0);
1411 }
1412 }
1413 });
1414 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1415 } else {
1416 settingsButton.setVisibility(View.GONE);
1417 }
1418
1419 mOverviewPanel.setAlpha(0f);
1420
1421 // Setup the workspace
1422 mWorkspace.setHapticFeedbackEnabled(false);
1423 mWorkspace.setOnLongClickListener(this);
1424 mWorkspace.setup(dragController);
1425 dragController.addDragListener(mWorkspace);
1426
1427 // Get the search/delete bar
1428 mSearchDropTargetBar = (SearchDropTargetBar)
1429 mDragLayer.findViewById(R.id.search_drop_target_bar);
1430
1431 // Setup Apps
1432 mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
1433
1434 // Setup AppsCustomize
1435 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
1436 mAppsCustomizeContent = (AppsCustomizePagedView)
1437 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
1438 mAppsCustomizeContent.setup(this, dragController);
1439
1440 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1441 dragController.setDragScoller(mWorkspace);
1442 dragController.setScrollView(mDragLayer);
1443 dragController.setMoveTarget(mWorkspace);
1444 dragController.addDropTarget(mWorkspace);
1445 if (mSearchDropTargetBar != null) {
1446 mSearchDropTargetBar.setup(this, dragController);
1447 if (getOrCreateQsbBar() == null) {
1448 // Explicitly set it to null during initialization.
1449 mSearchDropTargetBar.setQsbSearchBar(null);
1450 }
1451 }
1452
1453 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1454 Log.v(TAG, "adding WeightWatcher");
1455 mWeightWatcher = new WeightWatcher(this);
1456 mWeightWatcher.setAlpha(0.5f);
1457 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1458 new FrameLayout.LayoutParams(
1459 FrameLayout.LayoutParams.MATCH_PARENT,
1460 FrameLayout.LayoutParams.WRAP_CONTENT,
1461 Gravity.BOTTOM)
1462 );
1463
1464 boolean show = shouldShowWeightWatcher();
1465 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1466 }
1467 }
1468
1469 /**
1470 * Sets the all apps button. This method is called from {@link Hotseat}.
1471 */
1472 public void setAllAppsButton(View allAppsButton) {
1473 mAllAppsButton = allAppsButton;
1474 }
1475
1476 public View getAllAppsButton() {
1477 return mAllAppsButton;
1478 }
1479
1480 /**
1481 * Creates a view representing a shortcut.
1482 *
1483 * @param info The data structure describing the shortcut.
1484 *
1485 * @return A View inflated from R.layout.application.
1486 */
1487 View createShortcut(ShortcutInfo info) {
1488 return createShortcut(R.layout.application,
1489 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1490 }
1491
1492 /**
1493 * Creates a view representing a shortcut inflated from the specified resource.
1494 *
1495 * @param layoutResId The id of the XML layout used to create the shortcut.
1496 * @param parent The group the shortcut belongs to.
1497 * @param info The data structure describing the shortcut.
1498 *
1499 * @return A View inflated from layoutResId.
1500 */
1501 public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1502 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1503 favorite.applyFromShortcutInfo(info, mIconCache, true);
1504 favorite.setOnClickListener(this);
1505 favorite.setOnFocusChangeListener(mFocusHandler);
1506 return favorite;
1507 }
1508
1509 /**
1510 * Add a shortcut to the workspace.
1511 *
1512 * @param data The intent describing the shortcut.
1513 * @param cellInfo The position on screen where to create the shortcut.
1514 */
1515 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1516 int cellY) {
1517 int[] cellXY = mTmpAddItemCellCoordinates;
1518 int[] touchXY = mPendingAddInfo.dropPos;
1519 CellLayout layout = getCellLayout(container, screenId);
1520
1521 boolean foundCellSpan = false;
1522
1523 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
1524 if (info == null) {
1525 return;
1526 }
1527 final View view = createShortcut(info);
1528
1529 // First we check if we already know the exact location where we want to add this item.
1530 if (cellX >= 0 && cellY >= 0) {
1531 cellXY[0] = cellX;
1532 cellXY[1] = cellY;
1533 foundCellSpan = true;
1534
1535 // If appropriate, either create a folder or add to an existing folder
1536 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1537 true, null,null)) {
1538 return;
1539 }
1540 DragObject dragObject = new DragObject();
1541 dragObject.dragInfo = info;
1542 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1543 true)) {
1544 return;
1545 }
1546 } else if (touchXY != null) {
1547 // when dragging and dropping, just find the closest free spot
1548 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1549 foundCellSpan = (result != null);
1550 } else {
1551 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1552 }
1553
1554 if (!foundCellSpan) {
1555 showOutOfSpaceMessage(isHotseatLayout(layout));
1556 return;
1557 }
1558
1559 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
1560
1561 if (!mRestoring) {
1562 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1563 isWorkspaceLocked());
1564 }
1565 }
1566
1567 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1568 int minHeight) {
1569 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1570 // We want to account for the extra amount of padding that we are adding to the widget
1571 // to ensure that it gets the full amount of space that it has requested
1572 int requiredWidth = minWidth + padding.left + padding.right;
1573 int requiredHeight = minHeight + padding.top + padding.bottom;
1574 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1575 }
1576
1577 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1578 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1579 }
1580
1581 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1582 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1583 }
1584
1585 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1586 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1587 }
1588
1589 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1590 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1591 info.minResizeHeight);
1592 }
1593
1594 /**
1595 * Add a widget to the workspace.
1596 *
1597 * @param appWidgetId The app widget id
1598 */
1599 private void completeAddAppWidget(int appWidgetId, long container, long screenId,
1600 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1601
1602 ItemInfo info = mPendingAddInfo;
1603 if (appWidgetInfo == null) {
1604 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1605 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1606 }
1607
1608 if (appWidgetInfo.isCustomWidget) {
1609 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1610 }
1611
1612 LauncherAppWidgetInfo launcherInfo;
1613 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1614 launcherInfo.spanX = info.spanX;
1615 launcherInfo.spanY = info.spanY;
1616 launcherInfo.minSpanX = info.minSpanX;
1617 launcherInfo.minSpanY = info.minSpanY;
1618 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1619
1620 LauncherModel.addItemToDatabase(this, launcherInfo,
1621 container, screenId, info.cellX, info.cellY, false);
1622
1623 if (!mRestoring) {
1624 if (hostView == null) {
1625 // Perform actual inflation because we're live
1626 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1627 appWidgetInfo);
1628 } else {
1629 // The AppWidgetHostView has already been inflated and instantiated
1630 launcherInfo.hostView = hostView;
1631 }
1632 launcherInfo.hostView.setTag(launcherInfo);
1633 launcherInfo.hostView.setVisibility(View.VISIBLE);
1634 launcherInfo.notifyWidgetSizeChanged(this);
1635
1636 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1637 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1638
1639 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1640 }
1641 resetAddInfo();
1642 }
1643
1644 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1645 @Override
1646 public void onReceive(Context context, Intent intent) {
1647 final String action = intent.getAction();
1648 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1649 mUserPresent = false;
1650 mDragLayer.clearAllResizeFrames();
1651 updateAutoAdvanceState();
1652
1653 // Reset AllApps to its initial state only if we are not in the middle of
1654 // processing a multi-step drop
1655 if (mAppsView != null && mAppsCustomizeTabHost != null &&
1656 mPendingAddInfo.container == ItemInfo.NO_ID) {
1657 showWorkspace(false);
1658 }
1659 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1660 mUserPresent = true;
1661 updateAutoAdvanceState();
1662 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1663 mModel.resetLoadedState(false, true);
1664 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1665 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1666 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1667 mModel.resetLoadedState(false, true);
1668 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1669 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1670 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1671 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1672 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1673 getModel().forceReload();
1674 }
1675 }
1676 };
1677
1678 @Override
1679 public void onAttachedToWindow() {
1680 super.onAttachedToWindow();
1681
1682 // Listen for broadcasts related to user-presence
1683 final IntentFilter filter = new IntentFilter();
1684 filter.addAction(Intent.ACTION_SCREEN_OFF);
1685 filter.addAction(Intent.ACTION_USER_PRESENT);
1686 // For handling managed profiles
1687 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1688 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1689 if (ENABLE_DEBUG_INTENTS) {
1690 filter.addAction(DebugIntents.DELETE_DATABASE);
1691 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1692 }
1693 registerReceiver(mReceiver, filter);
1694 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1695 setupTransparentSystemBarsForLmp();
1696 mAttached = true;
1697 mVisible = true;
1698 }
1699
1700 /**
1701 * Sets up transparent navigation and status bars in LMP.
1702 * This method is a no-op for other platform versions.
1703 */
1704 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1705 private void setupTransparentSystemBarsForLmp() {
1706 if (Utilities.isLmpOrAbove()) {
1707 Window window = getWindow();
1708 window.getAttributes().systemUiVisibility |=
1709 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1710 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1711 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1712 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1713 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1714 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1715 window.setStatusBarColor(Color.TRANSPARENT);
1716 window.setNavigationBarColor(Color.TRANSPARENT);
1717 }
1718 }
1719
1720 @Override
1721 public void onDetachedFromWindow() {
1722 super.onDetachedFromWindow();
1723 mVisible = false;
1724
1725 if (mAttached) {
1726 unregisterReceiver(mReceiver);
1727 mAttached = false;
1728 }
1729 updateAutoAdvanceState();
1730 }
1731
1732 public void onWindowVisibilityChanged(int visibility) {
1733 mVisible = visibility == View.VISIBLE;
1734 updateAutoAdvanceState();
1735 // The following code used to be in onResume, but it turns out onResume is called when
1736 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1737 // is a more appropriate event to handle
1738 if (mVisible) {
1739 mAppsCustomizeTabHost.onWindowVisible();
1740 if (!mWorkspaceLoading) {
1741 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1742 // We want to let Launcher draw itself at least once before we force it to build
1743 // layers on all the workspace pages, so that transitioning to Launcher from other
1744 // apps is nice and speedy.
1745 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1746 private boolean mStarted = false;
1747 public void onDraw() {
1748 if (mStarted) return;
1749 mStarted = true;
1750 // We delay the layer building a bit in order to give
1751 // other message processing a time to run. In particular
1752 // this avoids a delay in hiding the IME if it was
1753 // currently shown, because doing that may involve
1754 // some communication back with the app.
1755 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1756 final ViewTreeObserver.OnDrawListener listener = this;
1757 mWorkspace.post(new Runnable() {
1758 public void run() {
1759 if (mWorkspace != null &&
1760 mWorkspace.getViewTreeObserver() != null) {
1761 mWorkspace.getViewTreeObserver().
1762 removeOnDrawListener(listener);
1763 }
1764 }
1765 });
1766 return;
1767 }
1768 });
1769 }
1770 clearTypedText();
1771 }
1772 }
1773
1774 private void sendAdvanceMessage(long delay) {
1775 mHandler.removeMessages(ADVANCE_MSG);
1776 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1777 mHandler.sendMessageDelayed(msg, delay);
1778 mAutoAdvanceSentTime = System.currentTimeMillis();
1779 }
1780
1781 private void updateAutoAdvanceState() {
1782 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1783 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1784 mAutoAdvanceRunning = autoAdvanceRunning;
1785 if (autoAdvanceRunning) {
1786 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1787 sendAdvanceMessage(delay);
1788 } else {
1789 if (!mWidgetsToAdvance.isEmpty()) {
1790 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1791 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1792 }
1793 mHandler.removeMessages(ADVANCE_MSG);
1794 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1795 }
1796 }
1797 }
1798
1799 private final Handler mHandler = new Handler() {
1800 @Override
1801 public void handleMessage(Message msg) {
1802 if (msg.what == ADVANCE_MSG) {
1803 int i = 0;
1804 for (View key: mWidgetsToAdvance.keySet()) {
1805 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1806 final int delay = mAdvanceStagger * i;
1807 if (v instanceof Advanceable) {
1808 postDelayed(new Runnable() {
1809 public void run() {
1810 ((Advanceable) v).advance();
1811 }
1812 }, delay);
1813 }
1814 i++;
1815 }
1816 sendAdvanceMessage(mAdvanceInterval);
1817 }
1818 }
1819 };
1820
1821 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1822 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1823 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1824 if (v instanceof Advanceable) {
1825 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1826 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1827 updateAutoAdvanceState();
1828 }
1829 }
1830
1831 void removeWidgetToAutoAdvance(View hostView) {
1832 if (mWidgetsToAdvance.containsKey(hostView)) {
1833 mWidgetsToAdvance.remove(hostView);
1834 updateAutoAdvanceState();
1835 }
1836 }
1837
1838 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1839 removeWidgetToAutoAdvance(launcherInfo.hostView);
1840 launcherInfo.hostView = null;
1841 }
1842
1843 void showOutOfSpaceMessage(boolean isHotseatLayout) {
1844 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1845 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1846 }
1847
1848 public DragLayer getDragLayer() {
1849 return mDragLayer;
1850 }
1851
1852 public AppsContainerView getAppsView() {
1853 return mAppsView;
1854 }
1855
1856 public AppsCustomizeTabHost getWidgetsView() {
1857 return mAppsCustomizeTabHost;
1858 }
1859
1860 public Workspace getWorkspace() {
1861 return mWorkspace;
1862 }
1863
1864 public Hotseat getHotseat() {
1865 return mHotseat;
1866 }
1867
1868 public ViewGroup getOverviewPanel() {
1869 return mOverviewPanel;
1870 }
1871
1872 public SearchDropTargetBar getSearchBar() {
1873 return mSearchDropTargetBar;
1874 }
1875
1876 public LauncherAppWidgetHost getAppWidgetHost() {
1877 return mAppWidgetHost;
1878 }
1879
1880 public LauncherModel getModel() {
1881 return mModel;
1882 }
1883
1884 protected SharedPreferences getSharedPrefs() {
1885 return mSharedPrefs;
1886 }
1887
1888 public void closeSystemDialogs() {
1889 getWindow().closeAllPanels();
1890
1891 // Whatever we were doing is hereby canceled.
1892 setWaitingForResult(false);
1893 }
1894
1895 @Override
1896 protected void onNewIntent(Intent intent) {
1897 long startTime = 0;
1898 if (DEBUG_RESUME_TIME) {
1899 startTime = System.currentTimeMillis();
1900 }
1901 super.onNewIntent(intent);
1902
1903 // Close the menu
1904 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1905 // also will cancel mWaitingForResult.
1906 closeSystemDialogs();
1907
1908 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1909 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1910 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1911
1912 if (mWorkspace == null) {
1913 // Can be cases where mWorkspace is null, this prevents a NPE
1914 return;
1915 }
1916 Folder openFolder = mWorkspace.getOpenFolder();
1917 // In all these cases, only animate if we're already on home
1918 mWorkspace.exitWidgetResizeMode();
1919
1920 boolean moveToDefaultScreen = mLauncherCallbacks != null ?
1921 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1922 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1923 openFolder == null && moveToDefaultScreen) {
1924 mWorkspace.moveToDefaultScreen(true);
1925 }
1926
1927 closeFolder();
1928 exitSpringLoadedDragMode();
1929
1930 // If we are already on home, then just animate back to the workspace,
1931 // otherwise, just wait until onResume to set the state back to Workspace
1932 if (alreadyOnHome) {
1933 showWorkspace(true);
1934 } else {
1935 mOnResumeState = State.WORKSPACE;
1936 }
1937
1938 final View v = getWindow().peekDecorView();
1939 if (v != null && v.getWindowToken() != null) {
1940 InputMethodManager imm = (InputMethodManager)getSystemService(
1941 INPUT_METHOD_SERVICE);
1942 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1943 }
1944
1945 // Reset the apps view
1946 if (!alreadyOnHome && mAppsView != null) {
1947 mAppsView.scrollToTop();
1948 }
1949
1950 // Reset the apps customize page
1951 if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
1952 mAppsCustomizeTabHost.reset();
1953 }
1954
1955 if (mLauncherCallbacks != null) {
1956 mLauncherCallbacks.onHomeIntent();
1957 }
1958 }
1959
1960 if (DEBUG_RESUME_TIME) {
1961 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1962 }
1963
1964 if (mLauncherCallbacks != null) {
1965 mLauncherCallbacks.onNewIntent(intent);
1966 }
1967 }
1968
1969 @Override
1970 public void onRestoreInstanceState(Bundle state) {
1971 super.onRestoreInstanceState(state);
1972 for (int page: mSynchronouslyBoundPages) {
1973 mWorkspace.restoreInstanceStateForChild(page);
1974 }
1975 }
1976
1977 @Override
1978 protected void onSaveInstanceState(Bundle outState) {
1979 if (mWorkspace.getChildCount() > 0) {
1980 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1981 mWorkspace.getCurrentPageOffsetFromCustomContent());
1982 }
1983 super.onSaveInstanceState(outState);
1984
1985 outState.putInt(RUNTIME_STATE, mState.ordinal());
1986 // We close any open folder since it will not be re-opened, and we need to make sure
1987 // this state is reflected.
1988 closeFolder();
1989
1990 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
1991 mWaitingForResult) {
1992 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1993 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1994 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1995 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1996 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1997 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1998 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1999 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
2000 }
2001
2002 if (mFolderInfo != null && mWaitingForResult) {
2003 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
2004 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
2005 }
2006
2007 // Save the current AppsCustomize tab
2008 if (mAppsCustomizeTabHost != null) {
2009 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
2010 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
2011 if (currentTabTag != null) {
2012 outState.putString("apps_customize_currentTab", currentTabTag);
2013 }
2014 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
2015 outState.putInt("apps_customize_currentIndex", currentIndex);
2016 }
2017 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2018
2019 if (mLauncherCallbacks != null) {
2020 mLauncherCallbacks.onSaveInstanceState(outState);
2021 }
2022 }
2023
2024 @Override
2025 public void onDestroy() {
2026 super.onDestroy();
2027
2028 // Remove all pending runnables
2029 mHandler.removeMessages(ADVANCE_MSG);
2030 mHandler.removeMessages(0);
2031 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2032
2033 // Stop callbacks from LauncherModel
2034 LauncherAppState app = (LauncherAppState.getInstance());
2035
2036 // It's possible to receive onDestroy after a new Launcher activity has
2037 // been created. In this case, don't interfere with the new Launcher.
2038 if (mModel.isCurrentCallbacks(this)) {
2039 mModel.stopLoader();
2040 app.setLauncher(null);
2041 }
2042
2043 try {
2044 mAppWidgetHost.stopListening();
2045 } catch (NullPointerException ex) {
2046 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2047 }
2048 mAppWidgetHost = null;
2049
2050 mWidgetsToAdvance.clear();
2051
2052 TextKeyListener.getInstance().release();
2053
2054 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2055 // to prevent leaking Launcher activities on orientation change.
2056 if (mModel != null) {
2057 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2058 }
2059
2060 getContentResolver().unregisterContentObserver(mWidgetObserver);
2061 unregisterReceiver(mCloseSystemDialogsReceiver);
2062
2063 mDragLayer.clearAllResizeFrames();
2064 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2065 mWorkspace.removeAllWorkspaceScreens();
2066 mWorkspace = null;
2067 mDragController = null;
2068
2069 LauncherAnimUtils.onDestroyActivity();
2070
2071 if (mLauncherCallbacks != null) {
2072 mLauncherCallbacks.onDestroy();
2073 }
2074 }
2075
2076 public DragController getDragController() {
2077 return mDragController;
2078 }
2079
2080 @Override
2081 public void startActivityForResult(Intent intent, int requestCode) {
2082 if (requestCode >= 0) {
2083 setWaitingForResult(true);
2084 }
2085 super.startActivityForResult(intent, requestCode);
2086 }
2087
2088 /**
2089 * Indicates that we want global search for this activity by setting the globalSearch
2090 * argument for {@link #startSearch} to true.
2091 */
2092 @Override
2093 public void startSearch(String initialQuery, boolean selectInitialQuery,
2094 Bundle appSearchData, boolean globalSearch) {
2095
2096 showWorkspace(true);
2097
2098 if (initialQuery == null) {
2099 // Use any text typed in the launcher as the initial query
2100 initialQuery = getTypedText();
2101 }
2102 if (appSearchData == null) {
2103 appSearchData = new Bundle();
2104 appSearchData.putString("source", "launcher-search");
2105 }
2106 Rect sourceBounds = new Rect();
2107 if (mSearchDropTargetBar != null) {
2108 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2109 }
2110
2111 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2112 appSearchData, sourceBounds);
2113 if (clearTextImmediately) {
2114 clearTypedText();
2115 }
2116 }
2117
2118 /**
2119 * Start a text search.
2120 *
2121 * @return {@code true} if the search will start immediately, so any further keypresses
2122 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2123 * to buffer keypresses.
2124 */
2125 public boolean startSearch(String initialQuery,
2126 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2127 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2128 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2129 sourceBounds);
2130 }
2131
2132 startGlobalSearch(initialQuery, selectInitialQuery,
2133 appSearchData, sourceBounds);
2134 return false;
2135 }
2136
2137 /**
2138 * Starts the global search activity. This code is a copied from SearchManager
2139 */
2140 private void startGlobalSearch(String initialQuery,
2141 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2142 final SearchManager searchManager =
2143 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2144 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2145 if (globalSearchActivity == null) {
2146 Log.w(TAG, "No global search activity found.");
2147 return;
2148 }
2149 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2150 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2151 intent.setComponent(globalSearchActivity);
2152 // Make sure that we have a Bundle to put source in
2153 if (appSearchData == null) {
2154 appSearchData = new Bundle();
2155 } else {
2156 appSearchData = new Bundle(appSearchData);
2157 }
2158 // Set source to package name of app that starts global search if not set already.
2159 if (!appSearchData.containsKey("source")) {
2160 appSearchData.putString("source", getPackageName());
2161 }
2162 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2163 if (!TextUtils.isEmpty(initialQuery)) {
2164 intent.putExtra(SearchManager.QUERY, initialQuery);
2165 }
2166 if (selectInitialQuery) {
2167 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2168 }
2169 intent.setSourceBounds(sourceBounds);
2170 try {
2171 startActivity(intent);
2172 } catch (ActivityNotFoundException ex) {
2173 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2174 }
2175 }
2176
2177 public boolean isOnCustomContent() {
2178 return mWorkspace.isOnOrMovingToCustomContent();
2179 }
2180
2181 @Override
2182 public boolean onPrepareOptionsMenu(Menu menu) {
2183 super.onPrepareOptionsMenu(menu);
2184 if (!isOnCustomContent()) {
2185 // Close any open folders
2186 closeFolder();
2187 // Stop resizing any widgets
2188 mWorkspace.exitWidgetResizeMode();
2189 if (!mWorkspace.isInOverviewMode()) {
2190 // Show the overview mode
2191 showOverviewMode(true);
2192 } else {
2193 showWorkspace(true);
2194 }
2195 }
2196 if (mLauncherCallbacks != null) {
2197 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2198 }
2199
2200 return false;
2201 }
2202
2203 @Override
2204 public boolean onSearchRequested() {
2205 startSearch(null, false, null, true);
2206 // Use a custom animation for launching search
2207 return true;
2208 }
2209
2210 public boolean isWorkspaceLocked() {
2211 return mWorkspaceLoading || mWaitingForResult;
2212 }
2213
2214 public boolean isWorkspaceLoading() {
2215 return mWorkspaceLoading;
2216 }
2217
2218 private void setWorkspaceLoading(boolean value) {
2219 boolean isLocked = isWorkspaceLocked();
2220 mWorkspaceLoading = value;
2221 if (isLocked != isWorkspaceLocked()) {
2222 onWorkspaceLockedChanged();
2223 }
2224 }
2225
2226 private void setWaitingForResult(boolean value) {
2227 boolean isLocked = isWorkspaceLocked();
2228 mWaitingForResult = value;
2229 if (isLocked != isWorkspaceLocked()) {
2230 onWorkspaceLockedChanged();
2231 }
2232 }
2233
2234 protected void onWorkspaceLockedChanged() {
2235 if (mLauncherCallbacks != null) {
2236 mLauncherCallbacks.onWorkspaceLockedChanged();
2237 }
2238 }
2239
2240 private void resetAddInfo() {
2241 mPendingAddInfo.container = ItemInfo.NO_ID;
2242 mPendingAddInfo.screenId = -1;
2243 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2244 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2245 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2246 mPendingAddInfo.dropPos = null;
2247 }
2248
2249 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2250 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2251 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2252 }
2253
2254 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2255 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2256 int delay) {
2257 if (appWidgetInfo.configure != null) {
2258 mPendingAddWidgetInfo = appWidgetInfo;
2259 mPendingAddWidgetId = appWidgetId;
2260
2261 // Launch over to configure widget, if needed
2262 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2263 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2264
2265 } else {
2266 // Otherwise just add it
2267 Runnable onComplete = new Runnable() {
2268 @Override
2269 public void run() {
2270 // Exit spring loaded mode if necessary after adding the widget
2271 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2272 null);
2273 }
2274 };
2275 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2276 appWidgetInfo);
2277 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2278 }
2279 }
2280
2281 protected void moveToCustomContentScreen(boolean animate) {
2282 // Close any folders that may be open.
2283 closeFolder();
2284 mWorkspace.moveToCustomContentScreen(animate);
2285 }
2286
2287 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2288 int[] cell, int spanX, int spanY) {
2289 switch (info.itemType) {
2290 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2291 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2292 int span[] = new int[2];
2293 span[0] = spanX;
2294 span[1] = spanY;
2295 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2296 container, screenId, cell, span);
2297 break;
2298 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2299 processShortcutFromDrop(info.componentName, container, screenId, cell);
2300 break;
2301 default:
2302 throw new IllegalStateException("Unknown item type: " + info.itemType);
2303 }
2304 }
2305
2306 /**
2307 * Process a shortcut drop.
2308 *
2309 * @param componentName The name of the component
2310 * @param screenId The ID of the screen where it should be added
2311 * @param cell The cell it should be added to, optional
2312 */
2313 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2314 int[] cell) {
2315 resetAddInfo();
2316 mPendingAddInfo.container = container;
2317 mPendingAddInfo.screenId = screenId;
2318 mPendingAddInfo.dropPos = null;
2319
2320 if (cell != null) {
2321 mPendingAddInfo.cellX = cell[0];
2322 mPendingAddInfo.cellY = cell[1];
2323 }
2324
2325 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2326 createShortcutIntent.setComponent(componentName);
2327 processShortcut(createShortcutIntent);
2328 }
2329
2330 /**
2331 * Process a widget drop.
2332 *
2333 * @param info The PendingAppWidgetInfo of the widget being added.
2334 * @param screenId The ID of the screen where it should be added
2335 * @param cell The cell it should be added to, optional
2336 */
2337 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2338 int[] cell, int[] span) {
2339 resetAddInfo();
2340 mPendingAddInfo.container = info.container = container;
2341 mPendingAddInfo.screenId = info.screenId = screenId;
2342 mPendingAddInfo.dropPos = null;
2343 mPendingAddInfo.minSpanX = info.minSpanX;
2344 mPendingAddInfo.minSpanY = info.minSpanY;
2345
2346 if (cell != null) {
2347 mPendingAddInfo.cellX = cell[0];
2348 mPendingAddInfo.cellY = cell[1];
2349 }
2350 if (span != null) {
2351 mPendingAddInfo.spanX = span[0];
2352 mPendingAddInfo.spanY = span[1];
2353 }
2354
2355 AppWidgetHostView hostView = info.boundWidget;
2356 int appWidgetId;
2357 if (hostView != null) {
2358 appWidgetId = hostView.getAppWidgetId();
2359 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2360 } else {
2361 // In this case, we either need to start an activity to get permission to bind
2362 // the widget, or we need to start an activity to configure the widget, or both.
2363 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2364 Bundle options = info.bindOptions;
2365
2366 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2367 appWidgetId, info.info, options);
2368 if (success) {
2369 addAppWidgetImpl(appWidgetId, info, null, info.info);
2370 } else {
2371 mPendingAddWidgetInfo = info.info;
2372 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2373 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2374 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2375 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2376 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2377 // TODO: we need to make sure that this accounts for the options bundle.
2378 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2379 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2380 }
2381 }
2382 }
2383
2384 void processShortcut(Intent intent) {
2385 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2386 }
2387
2388 void processWallpaper(Intent intent) {
2389 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2390 }
2391
2392 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2393 int cellY) {
2394 final FolderInfo folderInfo = new FolderInfo();
2395 folderInfo.title = getText(R.string.folder_name);
2396
2397 // Update the model
2398 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
2399 false);
2400 sFolders.put(folderInfo.id, folderInfo);
2401
2402 // Create the view
2403 FolderIcon newFolder =
2404 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2405 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2406 isWorkspaceLocked());
2407 // Force measure the new folder icon
2408 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2409 parent.getShortcutsAndWidgets().measureChild(newFolder);
2410 return newFolder;
2411 }
2412
2413 void removeFolder(FolderInfo folder) {
2414 sFolders.remove(folder.id);
2415 }
2416
2417 protected ComponentName getWallpaperPickerComponent() {
2418 if (mLauncherCallbacks != null) {
2419 return mLauncherCallbacks.getWallpaperPickerComponent();
2420 }
2421 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
2422 }
2423
2424 /**
2425 * Registers various content observers. The current implementation registers
2426 * only a favorites observer to keep track of the favorites applications.
2427 */
2428 private void registerContentObservers() {
2429 ContentResolver resolver = getContentResolver();
2430 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2431 true, mWidgetObserver);
2432 }
2433
2434 @Override
2435 public boolean dispatchKeyEvent(KeyEvent event) {
2436 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2437 switch (event.getKeyCode()) {
2438 case KeyEvent.KEYCODE_HOME:
2439 return true;
2440 case KeyEvent.KEYCODE_VOLUME_DOWN:
2441 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2442 dumpState();
2443 return true;
2444 }
2445 break;
2446 }
2447 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2448 switch (event.getKeyCode()) {
2449 case KeyEvent.KEYCODE_HOME:
2450 return true;
2451 }
2452 }
2453
2454 return super.dispatchKeyEvent(event);
2455 }
2456
2457 @Override
2458 public void onBackPressed() {
2459 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2460 return;
2461 }
2462
2463 if (LauncherAppState.getInstance().getAccessibilityDelegate().onBackPressed()) {
2464 return;
2465 }
2466
2467 if (isAppsViewVisible()) {
2468 showWorkspace(true);
2469 } else if (isWidgetsViewVisible()) {
2470 showOverviewMode(true);
2471 } else if (mWorkspace.isInOverviewMode()) {
2472 mWorkspace.exitOverviewMode(true);
2473 } else if (mWorkspace.getOpenFolder() != null) {
2474 Folder openFolder = mWorkspace.getOpenFolder();
2475 if (openFolder.isEditingName()) {
2476 openFolder.dismissEditingName();
2477 } else {
2478 closeFolder();
2479 }
2480 } else {
2481 mWorkspace.exitWidgetResizeMode();
2482
2483 // Back button is a no-op here, but give at least some feedback for the button press
2484 mWorkspace.showOutlinesTemporarily();
2485 }
2486 }
2487
2488 /**
2489 * Re-listen when widgets are reset.
2490 */
2491 private void onAppWidgetReset() {
2492 if (mAppWidgetHost != null) {
2493 mAppWidgetHost.startListening();
2494 }
2495 }
2496
2497 /**
2498 * Launches the intent referred by the clicked shortcut.
2499 *
2500 * @param v The view representing the clicked shortcut.
2501 */
2502 public void onClick(View v) {
2503 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2504 // view has detached (it's possible for this to happen if the view is removed mid touch).
2505 if (v.getWindowToken() == null) {
2506 return;
2507 }
2508
2509 if (!mWorkspace.isFinishedSwitchingState()) {
2510 return;
2511 }
2512
2513 if (v instanceof Workspace) {
2514 if (mWorkspace.isInOverviewMode()) {
2515 mWorkspace.exitOverviewMode(true);
2516 }
2517 return;
2518 }
2519
2520 if (v instanceof CellLayout) {
2521 if (mWorkspace.isInOverviewMode()) {
2522 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2523 }
2524 }
2525
2526 Object tag = v.getTag();
2527 if (tag instanceof ShortcutInfo) {
2528 onClickAppShortcut(v);
2529 } else if (tag instanceof FolderInfo) {
2530 if (v instanceof FolderIcon) {
2531 onClickFolderIcon(v);
2532 }
2533 } else if (v == mAllAppsButton) {
2534 onClickAllAppsButton(v);
2535 } else if (tag instanceof AppInfo) {
2536 startAppShortcutOrInfoActivity(v);
2537 } else if (tag instanceof LauncherAppWidgetInfo) {
2538 if (v instanceof PendingAppWidgetHostView) {
2539 onClickPendingWidget((PendingAppWidgetHostView) v);
2540 }
2541 }
2542 }
2543
2544 public void onClickPagedViewIcon(View v) {
2545 startAppShortcutOrInfoActivity(v);
2546 if (mLauncherCallbacks != null) {
2547 mLauncherCallbacks.onClickPagedViewIcon(v);
2548 }
2549 }
2550
2551 public boolean onTouch(View v, MotionEvent event) {
2552 return false;
2553 }
2554
2555 /**
2556 * Event handler for the app widget view which has not fully restored.
2557 */
2558 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2559 if (mIsSafeModeEnabled) {
2560 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2561 return;
2562 }
2563
2564 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2565 if (v.isReadyForClickSetup()) {
2566 int widgetId = info.appWidgetId;
2567 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2568 if (appWidgetInfo != null) {
2569 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2570 this, appWidgetInfo);
2571 mPendingAddInfo.copyFrom(info);
2572 mPendingAddWidgetId = widgetId;
2573
2574 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2575 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2576 }
2577 } else if (info.installProgress < 0) {
2578 // The install has not been queued
2579 final String packageName = info.providerName.getPackageName();
2580 showBrokenAppInstallDialog(packageName,
2581 new DialogInterface.OnClickListener() {
2582 public void onClick(DialogInterface dialog, int id) {
2583 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2584 }
2585 });
2586 } else {
2587 // Download has started.
2588 final String packageName = info.providerName.getPackageName();
2589 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2590 }
2591 }
2592
2593 /**
2594 * Event handler for the "grid" button that appears on the home screen, which
2595 * enters all apps mode.
2596 *
2597 * @param v The view that was clicked.
2598 */
2599 protected void onClickAllAppsButton(View v) {
2600 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2601 if (isAppsViewVisible()) {
2602 showWorkspace(true);
2603 } else {
2604 showAppsView(true /* animated */, false /* resetListToTop */);
2605 }
2606 if (mLauncherCallbacks != null) {
2607 mLauncherCallbacks.onClickAllAppsButton(v);
2608 }
2609 }
2610
2611 private void showBrokenAppInstallDialog(final String packageName,
2612 DialogInterface.OnClickListener onSearchClickListener) {
2613 new AlertDialog.Builder(this)
2614 .setTitle(R.string.abandoned_promises_title)
2615 .setMessage(R.string.abandoned_promise_explanation)
2616 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2617 .setNeutralButton(R.string.abandoned_clean_this,
2618 new DialogInterface.OnClickListener() {
2619 public void onClick(DialogInterface dialog, int id) {
2620 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2621 mWorkspace.removeAbandonedPromise(packageName, user);
2622 }
2623 })
2624 .create().show();
2625 return;
2626 }
2627
2628 /**
2629 * Event handler for an app shortcut click.
2630 *
2631 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2632 */
2633 protected void onClickAppShortcut(final View v) {
2634 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2635 Object tag = v.getTag();
2636 if (!(tag instanceof ShortcutInfo)) {
2637 throw new IllegalArgumentException("Input must be a Shortcut");
2638 }
2639
2640 // Open shortcut
2641 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2642
2643 if (shortcut.isDisabled != 0) {
2644 int error = R.string.activity_not_available;
2645 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2646 error = R.string.safemode_shortcut_error;
2647 }
2648 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2649 return;
2650 }
2651
2652 final Intent intent = shortcut.intent;
2653
2654 // Check for special shortcuts
2655 if (intent.getComponent() != null) {
2656 final String shortcutClass = intent.getComponent().getClassName();
2657
2658 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2659 MemoryDumpActivity.startDump(this);
2660 return;
2661 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2662 toggleShowWeightWatcher();
2663 return;
2664 }
2665 }
2666
2667 // Check for abandoned promise
2668 if ((v instanceof BubbleTextView)
2669 && shortcut.isPromise()
2670 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2671 showBrokenAppInstallDialog(
2672 shortcut.getTargetComponent().getPackageName(),
2673 new DialogInterface.OnClickListener() {
2674 public void onClick(DialogInterface dialog, int id) {
2675 startAppShortcutOrInfoActivity(v);
2676 }
2677 });
2678 return;
2679 }
2680
2681 // Start activities
2682 startAppShortcutOrInfoActivity(v);
2683
2684 if (mLauncherCallbacks != null) {
2685 mLauncherCallbacks.onClickAppShortcut(v);
2686 }
2687 }
2688
2689 private void startAppShortcutOrInfoActivity(View v) {
2690 Object tag = v.getTag();
2691 final ShortcutInfo shortcut;
2692 final Intent intent;
2693 if (tag instanceof ShortcutInfo) {
2694 shortcut = (ShortcutInfo) tag;
2695 intent = shortcut.intent;
2696 int[] pos = new int[2];
2697 v.getLocationOnScreen(pos);
2698 intent.setSourceBounds(new Rect(pos[0], pos[1],
2699 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2700
2701 } else if (tag instanceof AppInfo) {
2702 shortcut = null;
2703 intent = ((AppInfo) tag).intent;
2704 } else {
2705 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2706 }
2707
2708 boolean success = startActivitySafely(v, intent, tag);
2709 mStats.recordLaunch(intent, shortcut);
2710
2711 if (success && v instanceof BubbleTextView) {
2712 mWaitingForResume = (BubbleTextView) v;
2713 mWaitingForResume.setStayPressed(true);
2714 }
2715 }
2716
2717 /**
2718 * Event handler for a folder icon click.
2719 *
2720 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2721 */
2722 protected void onClickFolderIcon(View v) {
2723 if (LOGD) Log.d(TAG, "onClickFolder");
2724 if (!(v instanceof FolderIcon)){
2725 throw new IllegalArgumentException("Input must be a FolderIcon");
2726 }
2727
2728 FolderIcon folderIcon = (FolderIcon) v;
2729 final FolderInfo info = folderIcon.getFolderInfo();
2730 Folder openFolder = mWorkspace.getFolderForTag(info);
2731
2732 // If the folder info reports that the associated folder is open, then verify that
2733 // it is actually opened. There have been a few instances where this gets out of sync.
2734 if (info.opened && openFolder == null) {
2735 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2736 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2737 info.opened = false;
2738 }
2739
2740 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2741 // Close any open folder
2742 closeFolder();
2743 // Open the requested folder
2744 openFolder(folderIcon);
2745 } else {
2746 // Find the open folder...
2747 int folderScreen;
2748 if (openFolder != null) {
2749 folderScreen = mWorkspace.getPageForView(openFolder);
2750 // .. and close it
2751 closeFolder(openFolder);
2752 if (folderScreen != mWorkspace.getCurrentPage()) {
2753 // Close any folder open on the current screen
2754 closeFolder();
2755 // Pull the folder onto this screen
2756 openFolder(folderIcon);
2757 }
2758 }
2759 }
2760
2761 if (mLauncherCallbacks != null) {
2762 mLauncherCallbacks.onClickFolderIcon(v);
2763 }
2764 }
2765
2766 /**
2767 * Event handler for the (Add) Widgets button that appears after a long press
2768 * on the home screen.
2769 */
2770 protected void onClickAddWidgetButton(View view) {
2771 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2772 if (mIsSafeModeEnabled) {
2773 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2774 } else {
2775 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2776 if (mLauncherCallbacks != null) {
2777 mLauncherCallbacks.onClickAddWidgetButton(view);
2778 }
2779 }
2780 }
2781
2782 /**
2783 * Event handler for the wallpaper picker button that appears after a long press
2784 * on the home screen.
2785 */
2786 protected void onClickWallpaperPicker(View v) {
2787 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2788 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
2789 pickWallpaper.setComponent(getWallpaperPickerComponent());
2790 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
2791
2792 if (mLauncherCallbacks != null) {
2793 mLauncherCallbacks.onClickWallpaperPicker(v);
2794 }
2795 }
2796
2797 /**
2798 * Event handler for a click on the settings button that appears after a long press
2799 * on the home screen.
2800 */
2801 protected void onClickSettingsButton(View v) {
2802 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2803 if (mLauncherCallbacks != null) {
2804 mLauncherCallbacks.onClickSettingsButton(v);
2805 }
2806 }
2807
2808 public void onTouchDownAllAppsButton(View v) {
2809 // Provide the same haptic feedback that the system offers for virtual keys.
2810 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2811 }
2812
2813 public void performHapticFeedbackOnTouchDown(View v) {
2814 // Provide the same haptic feedback that the system offers for virtual keys.
2815 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2816 }
2817
2818 public View.OnTouchListener getHapticFeedbackTouchListener() {
2819 if (mHapticFeedbackTouchListener == null) {
2820 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2821 @Override
2822 public boolean onTouch(View v, MotionEvent event) {
2823 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2824 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2825 }
2826 return false;
2827 }
2828 };
2829 }
2830 return mHapticFeedbackTouchListener;
2831 }
2832
2833 public void onDragStarted(View view) {
2834 if (isOnCustomContent()) {
2835 // Custom content screen doesn't participate in drag and drop. If on custom
2836 // content screen, move to default.
2837 moveWorkspaceToDefaultScreen();
2838 }
2839
2840 if (mLauncherCallbacks != null) {
2841 mLauncherCallbacks.onDragStarted(view);
2842 }
2843 }
2844
2845 /**
2846 * Called when the user stops interacting with the launcher.
2847 * This implies that the user is now on the homescreen and is not doing housekeeping.
2848 */
2849 protected void onInteractionEnd() {
2850 if (mLauncherCallbacks != null) {
2851 mLauncherCallbacks.onInteractionEnd();
2852 }
2853 }
2854
2855 /**
2856 * Called when the user starts interacting with the launcher.
2857 * The possible interactions are:
2858 * - open all apps
2859 * - reorder an app shortcut, or a widget
2860 * - open the overview mode.
2861 * This is a good time to stop doing things that only make sense
2862 * when the user is on the homescreen and not doing housekeeping.
2863 */
2864 protected void onInteractionBegin() {
2865 if (mLauncherCallbacks != null) {
2866 mLauncherCallbacks.onInteractionBegin();
2867 }
2868 }
2869
2870 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2871 try {
2872 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2873 launcherApps.showAppDetailsForProfile(componentName, user);
2874 } catch (SecurityException e) {
2875 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2876 Log.e(TAG, "Launcher does not have permission to launch settings");
2877 } catch (ActivityNotFoundException e) {
2878 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2879 Log.e(TAG, "Unable to launch settings");
2880 }
2881 }
2882
2883 // returns true if the activity was started
2884 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2885 UserHandleCompat user) {
2886 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2887 // System applications cannot be installed. For now, show a toast explaining that.
2888 // We may give them the option of disabling apps this way.
2889 int messageId = R.string.uninstall_system_app_text;
2890 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2891 return false;
2892 } else {
2893 String packageName = componentName.getPackageName();
2894 String className = componentName.getClassName();
2895 Intent intent = new Intent(
2896 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2897 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2898 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2899 if (user != null) {
2900 user.addToIntent(intent, Intent.EXTRA_USER);
2901 }
2902 startActivity(intent);
2903 return true;
2904 }
2905 }
2906
2907 boolean startActivity(View v, Intent intent, Object tag) {
2908 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2909 try {
2910 // Only launch using the new animation if the shortcut has not opted out (this is a
2911 // private contract between launcher and may be ignored in the future).
2912 boolean useLaunchAnimation = (v != null) &&
2913 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2914 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2915 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2916
2917 UserHandleCompat user = null;
2918 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2919 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2920 user = userManager.getUserForSerialNumber(serialNumber);
2921 }
2922
2923 Bundle optsBundle = null;
2924 if (useLaunchAnimation) {
2925 ActivityOptions opts = null;
2926 if (sClipRevealMethod != null) {
2927 // TODO: call method directly when Launcher3 can depend on M APIs
2928 int left = 0, top = 0;
2929 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2930 if (v instanceof TextView) {
2931 // Launch from center of icon, not entire view
2932 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2933 if (icon != null) {
2934 Rect bounds = icon.getBounds();
2935 left = (width - bounds.width()) / 2;
2936 top = v.getPaddingTop();
2937 width = bounds.width();
2938 height = bounds.height();
2939 }
2940 }
2941 try {
2942 opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
2943 left, top, width, height);
2944 } catch (IllegalAccessException e) {
2945 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2946 sClipRevealMethod = null;
2947 } catch (InvocationTargetException e) {
2948 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2949 sClipRevealMethod = null;
2950 }
2951 }
2952 if (opts == null) {
2953 opts = Utilities.isLmpOrAbove() ?
2954 ActivityOptions.makeCustomAnimation(this,
2955 R.anim.task_open_enter, R.anim.no_anim) :
2956 ActivityOptions.makeScaleUpAnimation(v, 0, 0,
2957 v.getMeasuredWidth(), v.getMeasuredHeight());
2958 }
2959 optsBundle = opts.toBundle();
2960 }
2961
2962 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2963 // Could be launching some bookkeeping activity
2964 startActivity(intent, optsBundle);
2965 } else {
2966 // TODO Component can be null when shortcuts are supported for secondary user
2967 launcherApps.startActivityForProfile(intent.getComponent(), user,
2968 intent.getSourceBounds(), optsBundle);
2969 }
2970 return true;
2971 } catch (SecurityException e) {
2972 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2973 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2974 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2975 "or use the exported attribute for this activity. "
2976 + "tag="+ tag + " intent=" + intent, e);
2977 }
2978 return false;
2979 }
2980
2981 boolean startActivitySafely(View v, Intent intent, Object tag) {
2982 boolean success = false;
2983 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2984 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2985 return false;
2986 }
2987 try {
2988 success = startActivity(v, intent, tag);
2989 } catch (ActivityNotFoundException e) {
2990 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2991 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2992 }
2993 return success;
2994 }
2995
2996 /**
2997 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2998 * in the DragLayer in the exact absolute location of the original FolderIcon.
2999 */
3000 private void copyFolderIconToImage(FolderIcon fi) {
3001 final int width = fi.getMeasuredWidth();
3002 final int height = fi.getMeasuredHeight();
3003
3004 // Lazy load ImageView, Bitmap and Canvas
3005 if (mFolderIconImageView == null) {
3006 mFolderIconImageView = new ImageView(this);
3007 }
3008 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3009 mFolderIconBitmap.getHeight() != height) {
3010 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3011 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3012 }
3013
3014 DragLayer.LayoutParams lp;
3015 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3016 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3017 } else {
3018 lp = new DragLayer.LayoutParams(width, height);
3019 }
3020
3021 // The layout from which the folder is being opened may be scaled, adjust the starting
3022 // view size by this scale factor.
3023 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3024 lp.customPosition = true;
3025 lp.x = mRectForFolderAnimation.left;
3026 lp.y = mRectForFolderAnimation.top;
3027 lp.width = (int) (scale * width);
3028 lp.height = (int) (scale * height);
3029
3030 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3031 fi.draw(mFolderIconCanvas);
3032 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3033 if (fi.getFolder() != null) {
3034 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3035 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3036 }
3037 // Just in case this image view is still in the drag layer from a previous animation,
3038 // we remove it and re-add it.
3039 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3040 mDragLayer.removeView(mFolderIconImageView);
3041 }
3042 mDragLayer.addView(mFolderIconImageView, lp);
3043 if (fi.getFolder() != null) {
3044 fi.getFolder().bringToFront();
3045 }
3046 }
3047
3048 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3049 if (fi == null) return;
3050 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3051 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3052 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3053
3054 FolderInfo info = (FolderInfo) fi.getTag();
3055 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3056 CellLayout cl = (CellLayout) fi.getParent().getParent();
3057 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3058 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3059 }
3060
3061 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3062 copyFolderIconToImage(fi);
3063 fi.setVisibility(View.INVISIBLE);
3064
3065 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3066 scaleX, scaleY);
3067 if (Utilities.isLmpOrAbove()) {
3068 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3069 }
3070 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3071 oa.start();
3072 }
3073
3074 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3075 if (fi == null) return;
3076 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3077 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3078 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3079
3080 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3081
3082 // We remove and re-draw the FolderIcon in-case it has changed
3083 mDragLayer.removeView(mFolderIconImageView);
3084 copyFolderIconToImage(fi);
3085 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3086 scaleX, scaleY);
3087 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3088 oa.addListener(new AnimatorListenerAdapter() {
3089 @Override
3090 public void onAnimationEnd(Animator animation) {
3091 if (cl != null) {
3092 cl.clearFolderLeaveBehind();
3093 // Remove the ImageView copy of the FolderIcon and make the original visible.
3094 mDragLayer.removeView(mFolderIconImageView);
3095 fi.setVisibility(View.VISIBLE);
3096 }
3097 }
3098 });
3099 oa.start();
3100 }
3101
3102 /**
3103 * Opens the user folder described by the specified tag. The opening of the folder
3104 * is animated relative to the specified View. If the View is null, no animation
3105 * is played.
3106 *
3107 * @param folderInfo The FolderInfo describing the folder to open.
3108 */
3109 public void openFolder(FolderIcon folderIcon) {
3110 Folder folder = folderIcon.getFolder();
3111 FolderInfo info = folder.mInfo;
3112
3113 info.opened = true;
3114
3115 // Just verify that the folder hasn't already been added to the DragLayer.
3116 // There was a one-off crash where the folder had a parent already.
3117 if (folder.getParent() == null) {
3118 mDragLayer.addView(folder);
3119 mDragController.addDropTarget((DropTarget) folder);
3120 } else {
3121 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3122 folder.getParent() + ").");
3123 }
3124 folder.animateOpen();
3125 growAndFadeOutFolderIcon(folderIcon);
3126
3127 // Notify the accessibility manager that this folder "window" has appeared and occluded
3128 // the workspace items
3129 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3130 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3131 }
3132
3133 public void closeFolder() {
3134 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3135 if (folder != null) {
3136 if (folder.isEditingName()) {
3137 folder.dismissEditingName();
3138 }
3139 closeFolder(folder);
3140 }
3141 }
3142
3143 void closeFolder(Folder folder) {
3144 folder.getInfo().opened = false;
3145
3146 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3147 if (parent != null) {
3148 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3149 shrinkAndFadeInFolderIcon(fi);
3150 }
3151 folder.animateClosed();
3152
3153 // Notify the accessibility manager that this folder "window" has disappeard and no
3154 // longer occludeds the workspace items
3155 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3156 }
3157
3158 public boolean onLongClick(View v) {
3159 if (!isDraggingEnabled()) return false;
3160 if (isWorkspaceLocked()) return false;
3161 if (mState != State.WORKSPACE) return false;
3162
3163 if (v instanceof Workspace) {
3164 if (!mWorkspace.isInOverviewMode()) {
3165 if (mWorkspace.enterOverviewMode()) {
3166 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3167 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3168 return true;
3169 } else {
3170 return false;
3171 }
3172 } else {
3173 return false;
3174 }
3175 }
3176
3177 CellLayout.CellInfo longClickCellInfo = null;
3178 View itemUnderLongClick = null;
3179 if (v.getTag() instanceof ItemInfo) {
3180 ItemInfo info = (ItemInfo) v.getTag();
3181 longClickCellInfo = new CellLayout.CellInfo(v, info);
3182 itemUnderLongClick = longClickCellInfo.cell;
3183 resetAddInfo();
3184 }
3185
3186 // The hotseat touch handling does not go through Workspace, and we always allow long press
3187 // on hotseat items.
3188 final boolean inHotseat = isHotseatLayout(v);
3189 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3190 if (allowLongPress && !mDragController.isDragging()) {
3191 if (itemUnderLongClick == null) {
3192 // User long pressed on empty space
3193 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3194 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3195 if (mWorkspace.isInOverviewMode()) {
3196 mWorkspace.startReordering(v);
3197 } else {
3198 mWorkspace.enterOverviewMode();
3199 }
3200 } else {
3201 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3202 mHotseat.getOrderInHotseat(
3203 longClickCellInfo.cellX,
3204 longClickCellInfo.cellY));
3205 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3206 // User long pressed on an item
3207 mWorkspace.startDrag(longClickCellInfo);
3208 }
3209 }
3210 }
3211 return true;
3212 }
3213
3214 boolean isHotseatLayout(View layout) {
3215 return mHotseat != null && layout != null &&
3216 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3217 }
3218
3219 /**
3220 * Returns the CellLayout of the specified container at the specified screen.
3221 */
3222 public CellLayout getCellLayout(long container, long screenId) {
3223 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3224 if (mHotseat != null) {
3225 return mHotseat.getLayout();
3226 } else {
3227 return null;
3228 }
3229 } else {
3230 return mWorkspace.getScreenWithId(screenId);
3231 }
3232 }
3233
3234 /**
3235 * For overridden classes.
3236 */
3237 public boolean isAllAppsVisible() {
3238 return isAppsViewVisible();
3239 }
3240
3241 public boolean isAppsViewVisible() {
3242 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3243 }
3244
3245 public boolean isWidgetsViewVisible() {
3246 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3247 }
3248
3249 private void setWorkspaceBackground(boolean workspace) {
3250 mLauncherView.setBackground(workspace ?
3251 mWorkspaceBackgroundDrawable : null);
3252 }
3253
3254 protected void changeWallpaperVisiblity(boolean visible) {
3255 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3256 int curflags = getWindow().getAttributes().flags
3257 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3258 if (wpflags != curflags) {
3259 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3260 }
3261 setWorkspaceBackground(visible);
3262 }
3263
3264 @Override
3265 public void onTrimMemory(int level) {
3266 super.onTrimMemory(level);
3267 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3268 // The widget preview db can result in holding onto over
3269 // 3MB of memory for caching which isn't necessary.
3270 SQLiteDatabase.releaseMemory();
3271
3272 // This clears all widget bitmaps from the widget tray
3273 if (mAppsCustomizeTabHost != null) {
3274 mAppsCustomizeTabHost.trimMemory();
3275 }
3276 }
3277 }
3278
3279 @Override
3280 public void onStateTransitionHideSearchBar() {
3281 // Hide the search bar
3282 if (mSearchDropTargetBar != null) {
3283 mSearchDropTargetBar.hideSearchBar(false /* animated */);
3284 }
3285 }
3286
3287 protected void showWorkspace(boolean animated) {
3288 showWorkspace(animated, null);
3289 }
3290
3291 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3292 if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) {
3293 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3294 mWorkspace.setVisibility(View.VISIBLE);
3295 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
3296 animated, onCompleteRunnable);
3297
3298 // Show the search bar (only animate if we were showing the drop target bar in spring
3299 // loaded mode)
3300 if (mSearchDropTargetBar != null) {
3301 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3302 }
3303
3304 // Set focus to the AppsCustomize button
3305 if (mAllAppsButton != null) {
3306 mAllAppsButton.requestFocus();
3307 }
3308 }
3309
3310 // Change the state *after* we've called all the transition code
3311 mState = State.WORKSPACE;
3312
3313 // Resume the auto-advance of widgets
3314 mUserPresent = true;
3315 updateAutoAdvanceState();
3316
3317 // Send an accessibility event to announce the context change
3318 getWindow().getDecorView()
3319 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3320
3321 onWorkspaceShown(animated);
3322 }
3323
3324 void showOverviewMode(boolean animated) {
3325 mWorkspace.setVisibility(View.VISIBLE);
3326 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
3327 animated, null /* onCompleteRunnable */);
3328 mState = State.WORKSPACE;
3329 onWorkspaceShown(animated);
3330 }
3331
3332 public void onWorkspaceShown(boolean animated) {
3333 }
3334
3335 /**
3336 * Shows the apps view.
3337 */
3338 void showAppsView(boolean animated, boolean resetListToTop) {
3339 if (resetListToTop) {
3340 mAppsView.scrollToTop();
3341 }
3342 showAppsOrWidgets(animated, State.APPS);
3343 }
3344
3345 /**
3346 * Shows the widgets view.
3347 */
3348 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3349 if (resetPageToZero) {
3350 mAppsCustomizeTabHost.reset();
3351 }
3352 showAppsOrWidgets(animated, State.WIDGETS);
3353 mAppsCustomizeTabHost.post(new Runnable() {
3354 @Override
3355 public void run() {
3356 // We post this in-case the all apps view isn't yet constructed.
3357 mAppsCustomizeTabHost.requestFocus();
3358 }
3359 });
3360 }
3361
3362 /**
3363 * Sets up the transition to show the apps/widgets view.
3364 */
3365 private void showAppsOrWidgets(boolean animated, State toState) {
3366 if (mState != State.WORKSPACE) return;
3367 if (toState != State.APPS && toState != State.WIDGETS) return;
3368
3369 if (toState == State.APPS) {
3370 mStateTransitionAnimation.startAnimationToAllApps(animated);
3371 } else {
3372 mStateTransitionAnimation.startAnimationToWidgets(animated);
3373 }
3374
3375 // Change the state *after* we've called all the transition code
3376 mState = toState;
3377
3378 // Pause the auto-advance of widgets until we are out of AllApps
3379 mUserPresent = false;
3380 updateAutoAdvanceState();
3381 closeFolder();
3382
3383 // Send an accessibility event to announce the context change
3384 getWindow().getDecorView()
3385 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3386 }
3387
3388 void enterSpringLoadedDragMode() {
3389 if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
3390 mState == State.WIDGETS_SPRING_LOADED) {
3391 return;
3392 }
3393
3394 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
3395 true /* animated */, null /* onCompleteRunnable */);
3396 mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3397 }
3398
3399 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3400 final Runnable onCompleteRunnable) {
3401 if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
3402
3403 mHandler.postDelayed(new Runnable() {
3404 @Override
3405 public void run() {
3406 if (successfulDrop) {
3407 // Before we show workspace, hide all apps again because
3408 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3409 // clean up our state transition functions
3410 mAppsCustomizeTabHost.setVisibility(View.GONE);
3411 showWorkspace(true, onCompleteRunnable);
3412 } else {
3413 exitSpringLoadedDragMode();
3414 }
3415 }
3416 }, delay);
3417 }
3418
3419 void exitSpringLoadedDragMode() {
3420 if (mState == State.APPS_SPRING_LOADED) {
3421 mStateTransitionAnimation.startAnimationToAllApps(true /* animated */);
3422 mState = State.APPS;
3423 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3424 mStateTransitionAnimation.startAnimationToWidgets(true /* animated */);
3425 mState = State.WIDGETS;
3426 }
3427 // Otherwise, we are not in spring loaded mode, so don't do anything.
3428 }
3429
3430 void lockAllApps() {
3431 // TODO
3432 }
3433
3434 void unlockAllApps() {
3435 // TODO
3436 }
3437
3438 protected void disableVoiceButtonProxy(boolean disable) {
3439 // NO-OP
3440 }
3441
3442 public View getOrCreateQsbBar() {
3443 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
3444 return mLauncherCallbacks.getQsbBar();
3445 }
3446
3447 if (mQsb == null) {
3448 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3449 if (searchProvider == null) {
3450 return null;
3451 }
3452
3453 Bundle opts = new Bundle();
3454 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
3455 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
3456
3457 SharedPreferences sp = getSharedPreferences(
3458 LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
3459 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3460 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3461 if (!searchProvider.provider.flattenToString().equals(
3462 sp.getString(QSB_WIDGET_PROVIDER, null))
3463 || (widgetInfo == null)
3464 || !widgetInfo.provider.equals(searchProvider.provider)) {
3465 // A valid widget is not already bound.
3466 if (widgetId > -1) {
3467 mAppWidgetHost.deleteAppWidgetId(widgetId);
3468 widgetId = -1;
3469 }
3470
3471 // Try to bind a new widget
3472 widgetId = mAppWidgetHost.allocateAppWidgetId();
3473
3474 if (!AppWidgetManagerCompat.getInstance(this)
3475 .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
3476 mAppWidgetHost.deleteAppWidgetId(widgetId);
3477 widgetId = -1;
3478 }
3479
3480 sp.edit()
3481 .putInt(QSB_WIDGET_ID, widgetId)
3482 .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
3483 .commit();
3484 }
3485
3486 if (widgetId != -1) {
3487 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3488 mQsb.updateAppWidgetOptions(opts);
3489 mQsb.setPadding(0, 0, 0, 0);
3490 mSearchDropTargetBar.addView(mQsb);
3491 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3492 }
3493 }
3494 return mQsb;
3495 }
3496
3497 @Override
3498 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3499 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3500 final List<CharSequence> text = event.getText();
3501 text.clear();
3502 // Populate event with a fake title based on the current state.
3503 if (mState == State.APPS) {
3504 text.add("Apps");
3505 } else if (mState == State.WIDGETS) {
3506 text.add("Widgets");
3507 } else {
3508 text.add(getString(R.string.all_apps_home_button_label));
3509 }
3510 return result;
3511 }
3512
3513 /**
3514 * Receives notifications when system dialogs are to be closed.
3515 */
3516 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3517 @Override
3518 public void onReceive(Context context, Intent intent) {
3519 closeSystemDialogs();
3520 }
3521 }
3522
3523 /**
3524 * Receives notifications whenever the appwidgets are reset.
3525 */
3526 private class AppWidgetResetObserver extends ContentObserver {
3527 public AppWidgetResetObserver() {
3528 super(new Handler());
3529 }
3530
3531 @Override
3532 public void onChange(boolean selfChange) {
3533 onAppWidgetReset();
3534 }
3535 }
3536
3537 /**
3538 * If the activity is currently paused, signal that we need to run the passed Runnable
3539 * in onResume.
3540 *
3541 * This needs to be called from incoming places where resources might have been loaded
3542 * while we are paused. That is becaues the Configuration might be wrong
3543 * when we're not running, and if it comes back to what it was when we
3544 * were paused, we are not restarted.
3545 *
3546 * Implementation of the method from LauncherModel.Callbacks.
3547 *
3548 * @return true if we are currently paused. The caller might be able to
3549 * skip some work in that case since we will come back again.
3550 */
3551 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3552 if (mPaused) {
3553 Log.i(TAG, "Deferring update until onResume");
3554 if (deletePreviousRunnables) {
3555 while (mBindOnResumeCallbacks.remove(run)) {
3556 }
3557 }
3558 mBindOnResumeCallbacks.add(run);
3559 return true;
3560 } else {
3561 return false;
3562 }
3563 }
3564
3565 private boolean waitUntilResume(Runnable run) {
3566 return waitUntilResume(run, false);
3567 }
3568
3569 public void addOnResumeCallback(Runnable run) {
3570 mOnResumeCallbacks.add(run);
3571 }
3572
3573 /**
3574 * If the activity is currently paused, signal that we need to re-run the loader
3575 * in onResume.
3576 *
3577 * This needs to be called from incoming places where resources might have been loaded
3578 * while we are paused. That is becaues the Configuration might be wrong
3579 * when we're not running, and if it comes back to what it was when we
3580 * were paused, we are not restarted.
3581 *
3582 * Implementation of the method from LauncherModel.Callbacks.
3583 *
3584 * @return true if we are currently paused. The caller might be able to
3585 * skip some work in that case since we will come back again.
3586 */
3587 public boolean setLoadOnResume() {
3588 if (mPaused) {
3589 Log.i(TAG, "setLoadOnResume");
3590 mOnResumeNeedsLoad = true;
3591 return true;
3592 } else {
3593 return false;
3594 }
3595 }
3596
3597 /**
3598 * Implementation of the method from LauncherModel.Callbacks.
3599 */
3600 public int getCurrentWorkspaceScreen() {
3601 if (mWorkspace != null) {
3602 return mWorkspace.getCurrentPage();
3603 } else {
3604 return SCREEN_COUNT / 2;
3605 }
3606 }
3607
3608 /**
3609 * Refreshes the shortcuts shown on the workspace.
3610 *
3611 * Implementation of the method from LauncherModel.Callbacks.
3612 */
3613 public void startBinding() {
3614 setWorkspaceLoading(true);
3615
3616 // If we're starting binding all over again, clear any bind calls we'd postponed in
3617 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3618 // from scratch again
3619 mBindOnResumeCallbacks.clear();
3620
3621 // Clear the workspace because it's going to be rebound
3622 mWorkspace.clearDropTargets();
3623 mWorkspace.removeAllWorkspaceScreens();
3624
3625 mWidgetsToAdvance.clear();
3626 if (mHotseat != null) {
3627 mHotseat.resetLayout();
3628 }
3629 }
3630
3631 @Override
3632 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3633 bindAddScreens(orderedScreenIds);
3634
3635 // If there are no screens, we need to have an empty screen
3636 if (orderedScreenIds.size() == 0) {
3637 mWorkspace.addExtraEmptyScreen();
3638 }
3639
3640 // Create the custom content page (this call updates mDefaultScreen which calls
3641 // setCurrentPage() so ensure that all pages are added before calling this).
3642 if (hasCustomContentToLeft()) {
3643 mWorkspace.createCustomContentContainer();
3644 populateCustomContentContainer();
3645 }
3646 }
3647
3648 @Override
3649 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3650 // Log to disk
3651 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3652 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3653 TextUtils.join(", ", orderedScreenIds), true);
3654 int count = orderedScreenIds.size();
3655 for (int i = 0; i < count; i++) {
3656 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3657 }
3658 }
3659
3660 @Override
3661 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
3662 final long screenId, final int[] cell, final int spanX, final int spanY) {
3663 showWorkspace(true, new Runnable() {
3664
3665 @Override
3666 public void run() {
3667 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
3668 addPendingItem(info, container, screenId, cell, spanX, spanY);
3669 }
3670 });
3671 }
3672
3673 private boolean shouldShowWeightWatcher() {
3674 String spKey = LauncherAppState.getSharedPreferencesKey();
3675 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3676 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3677
3678 return show;
3679 }
3680
3681 private void toggleShowWeightWatcher() {
3682 String spKey = LauncherAppState.getSharedPreferencesKey();
3683 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3684 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3685
3686 show = !show;
3687
3688 SharedPreferences.Editor editor = sp.edit();
3689 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3690 editor.commit();
3691
3692 if (mWeightWatcher != null) {
3693 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3694 }
3695 }
3696
3697 public void bindAppsAdded(final ArrayList<Long> newScreens,
3698 final ArrayList<ItemInfo> addNotAnimated,
3699 final ArrayList<ItemInfo> addAnimated,
3700 final ArrayList<AppInfo> addedApps) {
3701 Runnable r = new Runnable() {
3702 public void run() {
3703 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3704 }
3705 };
3706 if (waitUntilResume(r)) {
3707 return;
3708 }
3709
3710 // Add the new screens
3711 if (newScreens != null) {
3712 bindAddScreens(newScreens);
3713 }
3714
3715 // We add the items without animation on non-visible pages, and with
3716 // animations on the new page (which we will try and snap to).
3717 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3718 bindItems(addNotAnimated, 0,
3719 addNotAnimated.size(), false);
3720 }
3721 if (addAnimated != null && !addAnimated.isEmpty()) {
3722 bindItems(addAnimated, 0,
3723 addAnimated.size(), true);
3724 }
3725
3726 // Remove the extra empty screen
3727 mWorkspace.removeExtraEmptyScreen(false, false);
3728
3729 if (addedApps != null && mAppsView != null) {
3730 mAppsView.addApps(addedApps);
3731 }
3732 }
3733
3734 /**
3735 * Bind the items start-end from the list.
3736 *
3737 * Implementation of the method from LauncherModel.Callbacks.
3738 */
3739 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3740 final boolean forceAnimateIcons) {
3741 Runnable r = new Runnable() {
3742 public void run() {
3743 bindItems(shortcuts, start, end, forceAnimateIcons);
3744 }
3745 };
3746 if (waitUntilResume(r)) {
3747 return;
3748 }
3749
3750 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3751 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3752 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3753 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3754 Workspace workspace = mWorkspace;
3755 long newShortcutsScreenId = -1;
3756 for (int i = start; i < end; i++) {
3757 final ItemInfo item = shortcuts.get(i);
3758
3759 // Short circuit if we are loading dock items for a configuration which has no dock
3760 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3761 mHotseat == null) {
3762 continue;
3763 }
3764
3765 switch (item.itemType) {
3766 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3767 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3768 ShortcutInfo info = (ShortcutInfo) item;
3769 View shortcut = createShortcut(info);
3770
3771 /*
3772 * TODO: FIX collision case
3773 */
3774 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3775 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3776 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3777 View v = cl.getChildAt(item.cellX, item.cellY);
3778 Object tag = v.getTag();
3779 String desc = "Collision while binding workspace item: " + item
3780 + ". Collides with " + tag;
3781 if (LauncherAppState.isDogfoodBuild()) {
3782 throw (new RuntimeException(desc));
3783 } else {
3784 Log.d(TAG, desc);
3785 }
3786 }
3787 }
3788
3789 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3790 item.cellY, 1, 1);
3791 if (animateIcons) {
3792 // Animate all the applications up now
3793 shortcut.setAlpha(0f);
3794 shortcut.setScaleX(0f);
3795 shortcut.setScaleY(0f);
3796 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3797 newShortcutsScreenId = item.screenId;
3798 }
3799 break;
3800 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3801 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3802 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3803 (FolderInfo) item, mIconCache);
3804 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3805 item.cellY, 1, 1);
3806 break;
3807 default:
3808 throw new RuntimeException("Invalid Item Type");
3809 }
3810 }
3811
3812 if (animateIcons) {
3813 // Animate to the correct page
3814 if (newShortcutsScreenId > -1) {
3815 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3816 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3817 final Runnable startBounceAnimRunnable = new Runnable() {
3818 public void run() {
3819 anim.playTogether(bounceAnims);
3820 anim.start();
3821 }
3822 };
3823 if (newShortcutsScreenId != currentScreenId) {
3824 // We post the animation slightly delayed to prevent slowdowns
3825 // when we are loading right after we return to launcher.
3826 mWorkspace.postDelayed(new Runnable() {
3827 public void run() {
3828 if (mWorkspace != null) {
3829 mWorkspace.snapToPage(newScreenIndex);
3830 mWorkspace.postDelayed(startBounceAnimRunnable,
3831 NEW_APPS_ANIMATION_DELAY);
3832 }
3833 }
3834 }, NEW_APPS_PAGE_MOVE_DELAY);
3835 } else {
3836 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3837 }
3838 }
3839 }
3840 workspace.requestLayout();
3841 }
3842
3843 /**
3844 * Implementation of the method from LauncherModel.Callbacks.
3845 */
3846 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3847 Runnable r = new Runnable() {
3848 public void run() {
3849 bindFolders(folders);
3850 }
3851 };
3852 if (waitUntilResume(r)) {
3853 return;
3854 }
3855 sFolders.clear();
3856 sFolders.putAll(folders);
3857 }
3858
3859 /**
3860 * Add the views for a widget to the workspace.
3861 *
3862 * Implementation of the method from LauncherModel.Callbacks.
3863 */
3864 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3865 Runnable r = new Runnable() {
3866 public void run() {
3867 bindAppWidget(item);
3868 }
3869 };
3870 if (waitUntilResume(r)) {
3871 return;
3872 }
3873
3874 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3875 if (DEBUG_WIDGETS) {
3876 Log.d(TAG, "bindAppWidget: " + item);
3877 }
3878 final Workspace workspace = mWorkspace;
3879
3880 LauncherAppWidgetProviderInfo appWidgetInfo =
3881 LauncherModel.getProviderInfo(this, item.providerName);
3882
3883 if (!mIsSafeModeEnabled
3884 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
3885 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
3886 if (appWidgetInfo == null) {
3887 if (DEBUG_WIDGETS) {
3888 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3889 + " belongs to component " + item.providerName
3890 + ", as the povider is null");
3891 }
3892 LauncherModel.deleteItemFromDatabase(this, item);
3893 return;
3894 }
3895 // Note: This assumes that the id remap broadcast is received before this step.
3896 // If that is not the case, the id remap will be ignored and user may see the
3897 // click to setup view.
3898 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
3899 pendingInfo.spanX = item.spanX;
3900 pendingInfo.spanY = item.spanY;
3901 pendingInfo.minSpanX = item.minSpanX;
3902 pendingInfo.minSpanY = item.minSpanY;
3903 Bundle options =
3904 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
3905
3906 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3907 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
3908 newWidgetId, appWidgetInfo, options);
3909
3910 // TODO consider showing a permission dialog when the widget is clicked.
3911 if (!success) {
3912 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3913 if (DEBUG_WIDGETS) {
3914 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3915 + " belongs to component " + item.providerName
3916 + ", as the launcher is unable to bing a new widget id");
3917 }
3918 LauncherModel.deleteItemFromDatabase(this, item);
3919 return;
3920 }
3921
3922 item.appWidgetId = newWidgetId;
3923
3924 // If the widget has a configure activity, it is still needs to set it up, otherwise
3925 // the widget is ready to go.
3926 item.restoreStatus = (appWidgetInfo.configure == null)
3927 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
3928 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3929
3930 LauncherModel.updateItemInDatabase(this, item);
3931 }
3932
3933 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
3934 final int appWidgetId = item.appWidgetId;
3935 if (DEBUG_WIDGETS) {
3936 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
3937 + appWidgetInfo.provider);
3938 }
3939
3940 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3941 } else {
3942 appWidgetInfo = null;
3943 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
3944 mIsSafeModeEnabled);
3945 view.updateIcon(mIconCache);
3946 item.hostView = view;
3947 item.hostView.updateAppWidget(null);
3948 item.hostView.setOnClickListener(this);
3949 }
3950
3951 item.hostView.setTag(item);
3952 item.onBindAppWidget(this);
3953
3954 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
3955 item.cellY, item.spanX, item.spanY, false);
3956 if (!item.isCustomWidget()) {
3957 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3958 }
3959
3960 workspace.requestLayout();
3961
3962 if (DEBUG_WIDGETS) {
3963 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
3964 + (SystemClock.uptimeMillis()-start) + "ms");
3965 }
3966 }
3967
3968 /**
3969 * Restores a pending widget.
3970 *
3971 * @param appWidgetId The app widget id
3972 * @param cellInfo The position on screen where to create the widget.
3973 */
3974 private void completeRestoreAppWidget(final int appWidgetId) {
3975 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
3976 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
3977 Log.e(TAG, "Widget update called, when the widget no longer exists.");
3978 return;
3979 }
3980
3981 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
3982 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
3983
3984 mWorkspace.reinflateWidgetsIfNecessary();
3985 LauncherModel.updateItemInDatabase(this, info);
3986 }
3987
3988 public void onPageBoundSynchronously(int page) {
3989 mSynchronouslyBoundPages.add(page);
3990 }
3991
3992 /**
3993 * Callback saying that there aren't any more items to bind.
3994 *
3995 * Implementation of the method from LauncherModel.Callbacks.
3996 */
3997 public void finishBindingItems() {
3998 Runnable r = new Runnable() {
3999 public void run() {
4000 finishBindingItems();
4001 }
4002 };
4003 if (waitUntilResume(r)) {
4004 return;
4005 }
4006 if (mSavedState != null) {
4007 if (!mWorkspace.hasFocus()) {
4008 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4009 }
4010 mSavedState = null;
4011 }
4012
4013 mWorkspace.restoreInstanceStateForRemainingPages();
4014
4015 setWorkspaceLoading(false);
4016 sendLoadingCompleteBroadcastIfNecessary();
4017
4018 // If we received the result of any pending adds while the loader was running (e.g. the
4019 // widget configuration forced an orientation change), process them now.
4020 if (sPendingAddItem != null) {
4021 final long screenId = completeAdd(sPendingAddItem);
4022
4023 // TODO: this moves the user to the page where the pending item was added. Ideally,
4024 // the screen would be guaranteed to exist after bind, and the page would be set through
4025 // the workspace restore process.
4026 mWorkspace.post(new Runnable() {
4027 @Override
4028 public void run() {
4029 mWorkspace.snapToScreenId(screenId);
4030 }
4031 });
4032 sPendingAddItem = null;
4033 }
4034
4035 PackageInstallerCompat.getInstance(this).onFinishBind();
4036
4037 if (mLauncherCallbacks != null) {
4038 mLauncherCallbacks.finishBindingItems(false);
4039 }
4040 }
4041
4042 private void sendLoadingCompleteBroadcastIfNecessary() {
4043 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4044 String permission =
4045 getResources().getString(R.string.receive_first_load_broadcast_permission);
4046 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4047 sendBroadcast(intent, permission);
4048 SharedPreferences.Editor editor = mSharedPrefs.edit();
4049 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4050 editor.apply();
4051 }
4052 }
4053
4054 public boolean isAllAppsButtonRank(int rank) {
4055 if (mHotseat != null) {
4056 return mHotseat.isAllAppsButtonRank(rank);
4057 }
4058 return false;
4059 }
4060
4061 private boolean canRunNewAppsAnimation() {
4062 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4063 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4064 }
4065
4066 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4067 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4068 PropertyValuesHolder.ofFloat("alpha", 1f),
4069 PropertyValuesHolder.ofFloat("scaleX", 1f),
4070 PropertyValuesHolder.ofFloat("scaleY", 1f));
4071 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4072 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4073 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4074 return bounceAnim;
4075 }
4076
4077 public boolean useVerticalBarLayout() {
4078 return LauncherAppState.getInstance().getDynamicGrid().
4079 getDeviceProfile().isVerticalBarLayout();
4080 }
4081
4082 protected Rect getSearchBarBounds() {
4083 return LauncherAppState.getInstance().getDynamicGrid().
4084 getDeviceProfile().getSearchBarBounds();
4085 }
4086
4087 public void bindSearchablesChanged() {
4088 if (mSearchDropTargetBar == null) {
4089 return;
4090 }
4091 if (mQsb != null) {
4092 mSearchDropTargetBar.removeView(mQsb);
4093 mQsb = null;
4094 }
4095 getOrCreateQsbBar();
4096 }
4097
4098 /**
4099 * Add the icons for all apps.
4100 *
4101 * Implementation of the method from LauncherModel.Callbacks.
4102 */
4103 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4104 if (mAppsView != null) {
4105 mAppsView.setApps(apps);
4106 }
4107 if (mAppsCustomizeContent != null) {
4108 mAppsCustomizeContent.onPackagesUpdated(
4109 LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
4110 <<<<<<< MINE
4111 ||||||| BASE
4112 mIntentsOnWorkspaceFromUpgradePath = null;
4113 }
4114 if (mAppsCustomizeContent != null) {
4115 mAppsCustomizeContent.onPackagesUpdated(
4116 LauncherModel.getSortedWidgetsAndShortcuts(this));
4117 }
4118 } else {
4119 if (mAppsCustomizeContent != null) {
4120 mAppsCustomizeContent.setApps(apps);
4121 mAppsCustomizeContent.onPackagesUpdated(
4122 LauncherModel.getSortedWidgetsAndShortcuts(this));
4123 }
4124 =======
4125 }
4126 } else {
4127 if (mAppsCustomizeContent != null) {
4128 mAppsCustomizeContent.setApps(apps);
4129 mAppsCustomizeContent.onPackagesUpdated(
4130 LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
4131 }
4132 >>>>>>> YOURS
4133 }
4134 if (mLauncherCallbacks != null) {
4135 mLauncherCallbacks.bindAllApplications(apps);
4136 }
4137 }
4138
4139 /**
4140 * A package was updated.
4141 *
4142 * Implementation of the method from LauncherModel.Callbacks.
4143 */
4144 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4145 Runnable r = new Runnable() {
4146 public void run() {
4147 bindAppsUpdated(apps);
4148 }
4149 };
4150 if (waitUntilResume(r)) {
4151 return;
4152 }
4153
4154 if (mAppsView != null) {
4155 mAppsView.updateApps(apps);
4156 }
4157 }
4158
4159 @Override
4160 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4161 Runnable r = new Runnable() {
4162 public void run() {
4163 bindWidgetsRestored(widgets);
4164 }
4165 };
4166 if (waitUntilResume(r)) {
4167 return;
4168 }
4169 mWorkspace.widgetsRestored(widgets);
4170 }
4171
4172 /**
4173 * Some shortcuts were updated in the background.
4174 *
4175 * Implementation of the method from LauncherModel.Callbacks.
4176 */
4177 @Override
4178 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4179 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4180 Runnable r = new Runnable() {
4181 public void run() {
4182 bindShortcutsChanged(updated, removed, user);
4183 }
4184 };
4185 if (waitUntilResume(r)) {
4186 return;
4187 }
4188
4189 if (!updated.isEmpty()) {
4190 mWorkspace.updateShortcuts(updated);
4191 }
4192
4193 if (!removed.isEmpty()) {
4194 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4195 for (ShortcutInfo si : removed) {
4196 removedComponents.add(si.getTargetComponent());
4197 }
4198 mWorkspace.removeItemsByComponentName(removedComponents, user);
4199 // Notify the drag controller
4200 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4201 }
4202 }
4203
4204 /**
4205 * Update the state of a package, typically related to install state.
4206 *
4207 * Implementation of the method from LauncherModel.Callbacks.
4208 */
4209 @Override
4210 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4211 if (mWorkspace != null) {
4212 mWorkspace.updatePackageState(installInfo);
4213 }
4214 }
4215
4216 /**
4217 * Update the label and icon of all the icons in a package
4218 *
4219 * Implementation of the method from LauncherModel.Callbacks.
4220 */
4221 @Override
4222 public void updatePackageBadge(String packageName) {
4223 if (mWorkspace != null) {
4224 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4225 }
4226 }
4227
4228 /**
4229 * A package was uninstalled. We take both the super set of packageNames
4230 * in addition to specific applications to remove, the reason being that
4231 * this can be called when a package is updated as well. In that scenario,
4232 * we only remove specific components from the workspace, where as
4233 * package-removal should clear all items by package name.
4234 *
4235 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4236 * Implementation of the method from LauncherModel.Callbacks.
4237 */
4238 @Override
4239 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4240 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4241 Runnable r = new Runnable() {
4242 public void run() {
4243 bindComponentsRemoved(packageNames, appInfos, user, reason);
4244 }
4245 };
4246 if (waitUntilResume(r)) {
4247 return;
4248 }
4249
4250 if (reason == 0) {
4251 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4252 for (AppInfo info : appInfos) {
4253 removedComponents.add(info.componentName);
4254 }
4255 if (!packageNames.isEmpty()) {
4256 mWorkspace.removeItemsByPackageName(packageNames, user);
4257 }
4258 if (!removedComponents.isEmpty()) {
4259 mWorkspace.removeItemsByComponentName(removedComponents, user);
4260 }
4261 // Notify the drag controller
4262 mDragController.onAppsRemoved(packageNames, removedComponents);
4263
4264 } else {
4265 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4266 }
4267
4268 // Update AllApps
4269 if (mAppsView != null) {
4270 mAppsView.removeApps(appInfos);
4271 }
4272 }
4273
4274 /**
4275 * A number of packages were updated.
4276 */
4277 private ArrayList<Object> mWidgetsAndShortcuts;
4278 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4279 public void run() {
4280 bindPackagesUpdated(mWidgetsAndShortcuts);
4281 mWidgetsAndShortcuts = null;
4282 }
4283 };
4284 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4285 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4286 mWidgetsAndShortcuts = widgetsAndShortcuts;
4287 return;
4288 }
4289
4290 // Update the widgets pane
4291 if (mAppsCustomizeContent != null) {
4292 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
4293 }
4294 }
4295
4296 private int mapConfigurationOriActivityInfoOri(int configOri) {
4297 final Display d = getWindowManager().getDefaultDisplay();
4298 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4299 switch (d.getRotation()) {
4300 case Surface.ROTATION_0:
4301 case Surface.ROTATION_180:
4302 // We are currently in the same basic orientation as the natural orientation
4303 naturalOri = configOri;
4304 break;
4305 case Surface.ROTATION_90:
4306 case Surface.ROTATION_270:
4307 // We are currently in the other basic orientation to the natural orientation
4308 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4309 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4310 break;
4311 }
4312
4313 int[] oriMap = {
4314 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4315 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4316 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4317 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4318 };
4319 // Since the map starts at portrait, we need to offset if this device's natural orientation
4320 // is landscape.
4321 int indexOffset = 0;
4322 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4323 indexOffset = 1;
4324 }
4325 return oriMap[(d.getRotation() + indexOffset) % 4];
4326 }
4327
4328 public void lockScreenOrientation() {
4329 if (Utilities.isRotationEnabled(this)) {
4330 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4331 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4332 .getConfiguration().orientation));
4333 } else {
4334 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4335 }
4336 }
4337 }
4338 public void unlockScreenOrientation(boolean immediate) {
4339 if (Utilities.isRotationEnabled(this)) {
4340 if (immediate) {
4341 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4342 } else {
4343 mHandler.postDelayed(new Runnable() {
4344 public void run() {
4345 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4346 }
4347 }, mRestoreScreenOrientationDelay);
4348 }
4349 }
4350 }
4351
4352 protected boolean isLauncherPreinstalled() {
4353 if (mLauncherCallbacks != null) {
4354 return mLauncherCallbacks.isLauncherPreinstalled();
4355 }
4356 PackageManager pm = getPackageManager();
4357 try {
4358 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4359 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4360 return true;
4361 } else {
4362 return false;
4363 }
4364 } catch (NameNotFoundException e) {
4365 e.printStackTrace();
4366 return false;
4367 }
4368 }
4369
4370 /**
4371 * This method indicates whether or not we should suggest default wallpaper dimensions
4372 * when our wallpaper cropper was not yet used to set a wallpaper.
4373 */
4374 protected boolean overrideWallpaperDimensions() {
4375 if (mLauncherCallbacks != null) {
4376 return mLauncherCallbacks.overrideWallpaperDimensions();
4377 }
4378 return true;
4379 }
4380
4381 /**
4382 * To be overridden by subclasses to indicate that there is an activity to launch
4383 * before showing the standard launcher experience.
4384 */
4385 protected boolean hasFirstRunActivity() {
4386 if (mLauncherCallbacks != null) {
4387 return mLauncherCallbacks.hasFirstRunActivity();
4388 }
4389 return false;
4390 }
4391
4392 /**
4393 * To be overridden by subclasses to launch any first run activity
4394 */
4395 protected Intent getFirstRunActivity() {
4396 if (mLauncherCallbacks != null) {
4397 return mLauncherCallbacks.getFirstRunActivity();
4398 }
4399 return null;
4400 }
4401
4402 private boolean shouldRunFirstRunActivity() {
4403 return !ActivityManager.isRunningInTestHarness() &&
4404 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4405 }
4406
4407 protected boolean hasRunFirstRunActivity() {
4408 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4409 }
4410
4411 public boolean showFirstRunActivity() {
4412 if (shouldRunFirstRunActivity() &&
4413 hasFirstRunActivity()) {
4414 Intent firstRunIntent = getFirstRunActivity();
4415 if (firstRunIntent != null) {
4416 startActivity(firstRunIntent);
4417 markFirstRunActivityShown();
4418 return true;
4419 }
4420 }
4421 return false;
4422 }
4423
4424 private void markFirstRunActivityShown() {
4425 SharedPreferences.Editor editor = mSharedPrefs.edit();
4426 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4427 editor.apply();
4428 }
4429
4430 /**
4431 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4432 * screen that must be displayed and dismissed.
4433 */
4434 protected boolean hasDismissableIntroScreen() {
4435 if (mLauncherCallbacks != null) {
4436 return mLauncherCallbacks.hasDismissableIntroScreen();
4437 }
4438 return false;
4439 }
4440
4441 /**
4442 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4443 */
4444 protected View getIntroScreen() {
4445 if (mLauncherCallbacks != null) {
4446 return mLauncherCallbacks.getIntroScreen();
4447 }
4448 return null;
4449 }
4450
4451 /**
4452 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4453 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4454 */
4455 private boolean shouldShowIntroScreen() {
4456 return hasDismissableIntroScreen() &&
4457 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4458 }
4459
4460 protected void showIntroScreen() {
4461 View introScreen = getIntroScreen();
4462 changeWallpaperVisiblity(false);
4463 if (introScreen != null) {
4464 mDragLayer.showOverlayView(introScreen);
4465 }
4466 if (mLauncherOverlayContainer != null) {
4467 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4468 }
4469 }
4470
4471 public void dismissIntroScreen() {
4472 markIntroScreenDismissed();
4473 if (showFirstRunActivity()) {
4474 // We delay hiding the intro view until the first run activity is showing. This
4475 // avoids a blip.
4476 mWorkspace.postDelayed(new Runnable() {
4477 @Override
4478 public void run() {
4479 mDragLayer.dismissOverlayView();
4480 if (mLauncherOverlayContainer != null) {
4481 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4482 }
4483 showFirstRunClings();
4484 }
4485 }, ACTIVITY_START_DELAY);
4486 } else {
4487 mDragLayer.dismissOverlayView();
4488 if (mLauncherOverlayContainer != null) {
4489 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4490 }
4491 showFirstRunClings();
4492 }
4493 changeWallpaperVisiblity(true);
4494 }
4495
4496 private void markIntroScreenDismissed() {
4497 SharedPreferences.Editor editor = mSharedPrefs.edit();
4498 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4499 editor.apply();
4500 }
4501
4502 private void showFirstRunClings() {
4503 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4504 // on the device, then we always show the first run cling experience (or if there is no
4505 // launcher2). Otherwise, we prompt the user upon started for migration
4506 LauncherClings launcherClings = new LauncherClings(this);
4507 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4508 if (mModel.canMigrateFromOldLauncherDb(this)) {
4509 launcherClings.showMigrationCling();
4510 } else {
4511 launcherClings.showLongPressCling(true);
4512 }
4513 }
4514 }
4515
4516 void showWorkspaceSearchAndHotseat() {
4517 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4518 if (mHotseat != null) mHotseat.setAlpha(1f);
4519 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4520 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4521 }
4522
4523 void hideWorkspaceSearchAndHotseat() {
4524 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4525 if (mHotseat != null) mHotseat.setAlpha(0f);
4526 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4527 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4528 }
4529
4530 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4531 // Called from search suggestion, not supported in other profiles.
4532 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4533 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4534 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
4535 myUser);
4536 if (activityInfo == null) {
4537 return null;
4538 }
4539 return new AppInfo(this, activityInfo, myUser, mIconCache);
4540 }
4541
4542 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4543 Bitmap icon) {
4544 // Called from search suggestion, not supported in other profiles.
4545 return createShortcutDragInfo(shortcutIntent, caption, icon,
4546 UserHandleCompat.myUserHandle());
4547 }
4548
4549 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4550 Bitmap icon, UserHandleCompat user) {
4551 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4552 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4553 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4554 }
4555
4556 protected void moveWorkspaceToDefaultScreen() {
4557 mWorkspace.moveToDefaultScreen(false);
4558 }
4559
4560 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4561 dragView.setTag(dragInfo);
4562 mWorkspace.onExternalDragStartedWithItem(dragView);
4563 mWorkspace.beginExternalDragShared(dragView, source);
4564 }
4565
4566 @Override
4567 public void onPageSwitch(View newPage, int newPageIndex) {
4568 if (mLauncherCallbacks != null) {
4569 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4570 }
4571 }
4572
4573 /**
4574 * Prints out out state for debugging.
4575 */
4576 public void dumpState() {
4577 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4578 Log.d(TAG, "mSavedState=" + mSavedState);
4579 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4580 Log.d(TAG, "mRestoring=" + mRestoring);
4581 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4582 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4583 Log.d(TAG, "sFolders.size=" + sFolders.size());
4584 mModel.dumpState();
4585
4586 if (mAppsCustomizeContent != null) {
4587 mAppsCustomizeContent.dumpState();
4588 }
4589 Log.d(TAG, "END launcher3 dump state");
4590 }
4591
4592 @Override
4593 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4594 super.dump(prefix, fd, writer, args);
4595 synchronized (sDumpLogs) {
4596 writer.println(" ");
4597 writer.println("Debug logs: ");
4598 for (int i = 0; i < sDumpLogs.size(); i++) {
4599 writer.println(" " + sDumpLogs.get(i));
4600 }
4601 }
4602 if (mLauncherCallbacks != null) {
4603 mLauncherCallbacks.dump(prefix, fd, writer, args);
4604 }
4605 }
4606
4607 public static void dumpDebugLogsToConsole() {
4608 if (DEBUG_DUMP_LOG) {
4609 synchronized (sDumpLogs) {
4610 Log.d(TAG, "");
4611 Log.d(TAG, "*********************");
4612 Log.d(TAG, "Launcher debug logs: ");
4613 for (int i = 0; i < sDumpLogs.size(); i++) {
4614 Log.d(TAG, " " + sDumpLogs.get(i));
4615 }
4616 Log.d(TAG, "*********************");
4617 Log.d(TAG, "");
4618 }
4619 }
4620 }
4621
4622 public static void addDumpLog(String tag, String log, boolean debugLog) {
4623 addDumpLog(tag, log, null, debugLog);
4624 }
4625
4626 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4627 if (debugLog) {
4628 if (e != null) {
4629 Log.d(tag, log, e);
4630 } else {
4631 Log.d(tag, log);
4632 }
4633 }
4634 if (DEBUG_DUMP_LOG) {
4635 sDateStamp.setTime(System.currentTimeMillis());
4636 synchronized (sDumpLogs) {
4637 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4638 + (e == null ? "" : (", Exception: " + e)));
4639 }
4640 }
4641 }
4642
4643 public static CustomAppWidget getCustomAppWidget(String name) {
4644 return sCustomAppWidgets.get(name);
4645 }
4646
4647 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4648 return sCustomAppWidgets;
4649 }
4650
4651 public void dumpLogsToLocalData() {
4652 if (DEBUG_DUMP_LOG) {
4653 new AsyncTask<Void, Void, Void>() {
4654 public Void doInBackground(Void ... args) {
4655 boolean success = false;
4656 sDateStamp.setTime(sRunStart);
4657 String FILENAME = sDateStamp.getMonth() + "-"
4658 + sDateStamp.getDay() + "_"
4659 + sDateStamp.getHours() + "-"
4660 + sDateStamp.getMinutes() + "_"
4661 + sDateStamp.getSeconds() + ".txt";
4662
4663 FileOutputStream fos = null;
4664 File outFile = null;
4665 try {
4666 outFile = new File(getFilesDir(), FILENAME);
4667 outFile.createNewFile();
4668 fos = new FileOutputStream(outFile);
4669 } catch (Exception e) {
4670 e.printStackTrace();
4671 }
4672 if (fos != null) {
4673 PrintWriter writer = new PrintWriter(fos);
4674
4675 writer.println(" ");
4676 writer.println("Debug logs: ");
4677 synchronized (sDumpLogs) {
4678 for (int i = 0; i < sDumpLogs.size(); i++) {
4679 writer.println(" " + sDumpLogs.get(i));
4680 }
4681 }
4682 writer.close();
4683 }
4684 try {
4685 if (fos != null) {
4686 fos.close();
4687 success = true;
4688 }
4689 } catch (IOException e) {
4690 e.printStackTrace();
4691 }
4692 return null;
4693 }
4694 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4695 }
4696 }
4697 }
4698
4699 interface LauncherTransitionable {
4700 View getContent();
4701 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
4702 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
4703 void onLauncherTransitionStep(Launcher l, float t);
4704 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
4705 }
4706
4707 interface DebugIntents {
4708 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
4709 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
4710 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.annotation.TargetApi;
25 import android.app.Activity;
26 import android.app.ActivityManager;
27 import android.app.ActivityOptions;
28 import android.app.AlertDialog;
29 import android.app.SearchManager;
30 import android.appwidget.AppWidgetHostView;
31 import android.appwidget.AppWidgetManager;
32 import android.appwidget.AppWidgetProviderInfo;
33 import android.content.ActivityNotFoundException;
34 import android.content.BroadcastReceiver;
35 import android.content.ComponentCallbacks2;
36 import android.content.ComponentName;
37 import android.content.ContentResolver;
38 import android.content.Context;
39 import android.content.DialogInterface;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.SharedPreferences;
43 import android.content.pm.ActivityInfo;
44 import android.content.pm.ApplicationInfo;
45 import android.content.pm.PackageManager.NameNotFoundException;
46 import android.content.pm.PackageManager;
47 import android.content.res.Configuration;
48 import android.database.ContentObserver;
49 import android.database.sqlite.SQLiteDatabase;
50 import android.graphics.Bitmap;
51 import android.graphics.Canvas;
52 import android.graphics.Color;
53 import android.graphics.PorterDuff;
54 import android.graphics.Rect;
55 import android.graphics.drawable.Drawable;
56 import android.net.Uri;
57 import android.os.AsyncTask;
58 import android.os.Build;
59 import android.os.Bundle;
60 import android.os.Environment;
61 import android.os.Handler;
62 import android.os.Message;
63 import android.os.StrictMode;
64 import android.os.SystemClock;
65 import android.text.Selection;
66 import android.text.SpannableStringBuilder;
67 import android.text.TextUtils;
68 import android.text.method.TextKeyListener;
69 import android.util.Log;
70 import android.view.Display;
71 import android.view.Gravity;
72 import android.view.HapticFeedbackConstants;
73 import android.view.KeyEvent;
74 import android.view.LayoutInflater;
75 import android.view.Menu;
76 import android.view.MotionEvent;
77 import android.view.Surface;
78 import android.view.View.OnClickListener;
79 import android.view.View.OnLongClickListener;
80 import android.view.View;
81 import android.view.ViewGroup;
82 import android.view.ViewStub;
83 import android.view.ViewTreeObserver;
84 import android.view.Window;
85 import android.view.WindowManager;
86 import android.view.accessibility.AccessibilityEvent;
87 import android.view.inputmethod.InputMethodManager;
88 import android.widget.Advanceable;
89 import android.widget.FrameLayout;
90 import android.widget.ImageView;
91 import android.widget.TextView;
92 import android.widget.Toast;
93 import com.android.launcher3.DropTarget.DragObject;
94 import com.android.launcher3.PagedView.PageSwitchListener;
95 import com.android.launcher3.compat.AppWidgetManagerCompat;
96 import com.android.launcher3.compat.LauncherActivityInfoCompat;
97 import com.android.launcher3.compat.LauncherAppsCompat;
98 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
99 import com.android.launcher3.compat.PackageInstallerCompat;
100 import com.android.launcher3.compat.UserHandleCompat;
101 import com.android.launcher3.compat.UserManagerCompat;
102 import java.io.DataInputStream;
103 import java.io.DataOutputStream;
104 import java.io.File;
105 import java.io.FileDescriptor;
106 import java.io.FileNotFoundException;
107 import java.io.FileOutputStream;
108 import java.io.IOException;
109 import java.io.PrintWriter;
110 import java.lang.reflect.InvocationTargetException;
111 import java.lang.reflect.Method;
112 import java.text.DateFormat;
113 import java.util.ArrayList;
114 import java.util.Collection;
115 import java.util.Date;
116 import java.util.HashMap;
117 import java.util.HashSet;
118 import java.util.List;
119 import java.util.concurrent.atomic.AtomicInteger;
120
121
122 interface DebugIntents {
123 public static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
124
125 public static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
126 }
127
128 /**
129 * Default launcher application.
130 */
131 public class Launcher extends Activity implements LauncherStateTransitionAnimation.Callbacks , View.OnCli🔵
132 static final String TAG = "Launcher";
133
134 static final boolean LOGD = false;
135
136 static final boolean PROFILE_STARTUP = false;
137
138 static final boolean DEBUG_WIDGETS = false;
139
140 static final boolean DEBUG_STRICT_MODE = false;
141
142 static final boolean DEBUG_RESUME_TIME = false;
143
144 static final boolean DEBUG_DUMP_LOG = false;
145
146 // allow DebugIntents to run
147 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
148
149 private static final int REQUEST_CREATE_SHORTCUT = 1;
150
151 private static final int REQUEST_CREATE_APPWIDGET = 5;
152
153 private static final int REQUEST_PICK_APPWIDGET = 9;
154
155 private static final int REQUEST_PICK_WALLPAPER = 10;
156
157 private static final int REQUEST_BIND_APPWIDGET = 11;
158
159 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
160
161 /**
162 * IntentStarter uses request codes starting with this. This must be greater than all activity
163 * request codes used internally.
164 */
165 protected static final int REQUEST_LAST = 100;
166
167 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
168
169 static final int SCREEN_COUNT = 5;
170
171 // To turn on these properties, type
172 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
173 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
174
175 // The Intent extra that defines whether to ignore the launch animation
176 // The Intent extra that defines whether to ignore the launch animation
177 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
178 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
179
180 // Type: int
181 // Type: int
182 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
183
184 // Type: int
185 // Type: int
186 private static final String RUNTIME_STATE = "launcher.state";
187
188 // Type: int
189 // Type: int
190 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
191
192 // Type: int
193 // Type: int
194 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
195
196 // Type: int
197 // Type: int
198 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
199
200 // Type: int
201 // Type: int
202 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
203
204 // Type: boolean
205 // Type: boolean
206 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
207
208 // Type: long
209 // Type: long
210 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
211
212 // Type: int
213 // Type: int
214 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
215
216 // Type: int
217 // Type: int
218 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
219
220 // Type: parcelable
221 // Type: parcelable
222 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
223
224 // Type: parcelable
225 // Type: parcelable
226 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
227
228 // Type: int[]
229 // Type: int[]
230 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
231
232 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
233
234 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
235
236 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
237
238 static final String ACTION_FIRST_LOAD_COMPLETE =
239 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
240
241 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
242
243 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
244
245 private static final String QSB_WIDGET_ID = "qsb_widget_id";
246
247 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
248
249 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
250
251 /** The different states that Launcher can be in. */
252 enum State {
253
254 NONE,
255 WORKSPACE,
256 APPS,
257 APPS_SPRING_LOADED,
258 WIDGETS,
259 WIDGETS_SPRING_LOADED;}
260
261 private State mState = State.WORKSPACE;
262
263 private AnimatorSet mStateAnimation;
264
265 private LauncherStateTransitionAnimation mStateTransitionAnimation;
266
267 private boolean mIsSafeModeEnabled;
268
269 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
270
271 LauncherOverlay mLauncherOverlay;
272
273 InsettableFrameLayout mLauncherOverlayContainer;
274
275 static final int APPWIDGET_HOST_ID = 1024;
276
277 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
278
279 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
280
281 private static final int ACTIVITY_START_DELAY = 1000;
282
283 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
284
285 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
286
287 // How long to wait before the new-shortcut animation automatically pans the workspace
288 // How long to wait before the new-shortcut animation automatically pans the workspace
289 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
290
291 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
292
293 private static int NEW_APPS_ANIMATION_DELAY = 500;
294
295 private final BroadcastReceiver mCloseSystemDialogsReceiver
296 = new CloseSystemDialogsIntentReceiver();
297
298 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
299
300 private LayoutInflater mInflater;
301
302 private Workspace mWorkspace;
303
304 private View mLauncherView;
305
306 private View mPageIndicators;
307
308 private DragLayer mDragLayer;
309
310 private DragController mDragController;
311
312 private View mWeightWatcher;
313
314 private AppWidgetManagerCompat mAppWidgetManager;
315
316 private LauncherAppWidgetHost mAppWidgetHost;
317
318 private ItemInfo mPendingAddInfo = new ItemInfo();
319
320 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
321
322 private int mPendingAddWidgetId = -1;
323
324 private int[] mTmpAddItemCellCoordinates = new int[2];
325
326 private FolderInfo mFolderInfo;
327
328 private Hotseat mHotseat;
329
330 private ViewGroup mOverviewPanel;
331
332 private View mAllAppsButton;
333
334 private SearchDropTargetBar mSearchDropTargetBar;
335
336 private AppsContainerView mAppsView;
337
338 private AppsCustomizeTabHost mAppsCustomizeTabHost;
339
340 private AppsCustomizePagedView mAppsCustomizeContent;
341
342 private boolean mAutoAdvanceRunning = false;
343
344 private AppWidgetHostView mQsb;
345
346 private Bundle mSavedState;
347
348 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
349 // scroll issues (because the workspace may not have been measured yet) and extra work.
350 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
351 private State mOnResumeState = State.NONE;
352
353 private SpannableStringBuilder mDefaultKeySsb = null;
354
355 private boolean mWorkspaceLoading = true;
356
357 private boolean mPaused = true;
358
359 private boolean mRestoring;
360
361 private boolean mWaitingForResult;
362
363 private boolean mOnResumeNeedsLoad;
364
365 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
366
367 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
368
369 private Bundle mSavedInstanceState;
370
371 private LauncherModel mModel;
372
373 private IconCache mIconCache;
374
375 private boolean mUserPresent = true;
376
377 private boolean mVisible = false;
378
379 private boolean mHasFocus = false;
380
381 private boolean mAttached = false;
382
383 private static LocaleConfiguration sLocaleConfiguration = null;
384
385 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
386
387 private View.OnTouchListener mHapticFeedbackTouchListener;
388
389 // Related to the auto-advancing of widgets
390 // Related to the auto-advancing of widgets
391 private final int ADVANCE_MSG = 1;
392
393 private final int mAdvanceInterval = 20000;
394
395 private final int mAdvanceStagger = 250;
396
397 private long mAutoAdvanceSentTime;
398
399 private long mAutoAdvanceTimeLeft = -1;
400
401 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<View, AppWidgetProviderI🔵
402
403 // Determines how long to wait after a rotation before restoring the screen orientation to
404 // match the sensor state.
405 // Determines how long to wait after a rotation before restoring the screen orientation to
406 // match the sensor state.
407 private final int mRestoreScreenOrientationDelay = 500;
408
409 private Drawable mWorkspaceBackgroundDrawable;
410
411 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
412
413 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
414
415 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
416
417 static Date sDateStamp = new Date();
418
419 static DateFormat sDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
420
421 static long sRunStart = System.currentTimeMillis();
422
423 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
424
425 // We only want to get the SharedPreferences once since it does an FS stat each time we get
426 // it from the context.
427 // We only want to get the SharedPreferences once since it does an FS stat each time we get
428 // it from the context.
429 private SharedPreferences mSharedPrefs;
430
431 // Holds the page that we need to animate to, and the icon views that we need to animate up
432 // when we scroll to that page on resume.
433 private ImageView mFolderIconImageView;
434
435 private Bitmap mFolderIconBitmap;
436
437 private Canvas mFolderIconCanvas;
438
439 private Rect mRectForFolderAnimation = new Rect();
440
441 private BubbleTextView mWaitingForResume;
442
443 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets = new HashMap<String, CustomAppWi🔵
444
445 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
446
447 static {
448 if (ENABLE_CUSTOM_WIDGET_TEST) {
449 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
450 }
451 }
452
453 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
454 private static Method sClipRevealMethod = null;
455
456 static {
457 Class<?> activityOptionsClass = ActivityOptions.class;
458 try {
459 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation", View.cl🔵
460 } catch (java.lang.Exception e) {
461 // Earlier version
462 }
463 }
464
465 private Runnable mBuildLayersRunnable = new Runnable() {
466 public void run() {
467 if (mWorkspace != null) {
468 mWorkspace.buildPageHardwareLayers();
469 }
470 }
471 };
472
473 private static PendingAddArguments sPendingAddItem;
474
475 private static class PendingAddArguments {
476 int requestCode;
477
478 Intent intent;
479
480 long container;
481
482 long screenId;
483
484 int cellX;
485
486 int cellY;
487
488 int appWidgetId;
489 }
490
491 private Stats mStats;
492
493 FocusIndicatorView mFocusHandler;
494
495 @Override
496 protected void onCreate(Bundle savedInstanceState) {
497 if (DEBUG_STRICT_MODE) {
498 StrictMode.setThreadPolicy(// or .detectAll() for all detectable problems
499 new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().pe🔵
500 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectL🔵
501 }
502 if (mLauncherCallbacks != null) {
503 mLauncherCallbacks.preOnCreate();
504 }
505 super.onCreate(savedInstanceState);
506 LauncherAppState.setApplicationContext(getApplicationContext());
507 LauncherAppState app = LauncherAppState.getInstance();
508 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
509 // Lazy-initialize the dynamic grid
510 DeviceProfile grid = app.initDynamicGrid(this);
511 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
512 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIV🔵
513 mIsSafeModeEnabled = getPackageManager().isSafeMode();
514 mModel = app.setLauncher(this);
515 mIconCache = app.getIconCache();
516 mIconCache.flushInvalidIcons(grid);
517 mDragController = new DragController(this);
518 mInflater = getLayoutInflater();
519 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
520 mStats = new Stats(this);
521 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
522 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
523 mAppWidgetHost.startListening();
524 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
525 // this also ensures that any synchronous binding below doesn't re-trigger another
526 // LauncherModel load.
527 mPaused = false;
528 if (PROFILE_STARTUP) {
529 android.os.Debug.startMethodTracing(Environment.getExternalStorageDirectory() + "/launcher");
530 }
531 checkForLocaleChange();
532 setContentView(R.layout.launcher);
533 setupViews();
534 grid.layout(this);
535 registerContentObservers();
536 lockAllApps();
537 mSavedState = savedInstanceState;
538 restoreState(mSavedState);
539 if (PROFILE_STARTUP) {
540 android.os.Debug.stopMethodTracing();
541 }
542 if (!mRestoring) {
543 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
544 // If the user leaves launcher, then we should just load items asynchronously when
545 // they return.
546 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
547 } else {
548 // We only load the page synchronously if the user rotates (or triggers a
549 // configuration change) while launcher is in the foreground
550 mModel.startLoader(true, mWorkspace.getRestorePage());
551 }
552 }
553 // For handling default keys
554 mDefaultKeySsb = new SpannableStringBuilder();
555 Selection.setSelection(mDefaultKeySsb, 0);
556 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
557 registerReceiver(mCloseSystemDialogsReceiver, filter);
558 // On large interfaces, we want the screen to auto-rotate based on the current orientation
559 unlockScreenOrientation(true);
560 if (mLauncherCallbacks != null) {
561 mLauncherCallbacks.onCreate(savedInstanceState);
562 if (mLauncherCallbacks.hasLauncherOverlay()) {
563 ViewStub stub = ((ViewStub) (findViewById(R.id.launcher_overlay_stub)));
564 mLauncherOverlayContainer = ((InsettableFrameLayout) (stub.inflate()));
565 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(mLauncherOverlayContainer, m🔵
566 mWorkspace.setLauncherOverlay(mLauncherOverlay);
567 }
568 }
569 if (shouldShowIntroScreen()) {
570 showIntroScreen();
571 } else {
572 showFirstRunActivity();
573 showFirstRunClings();
574 }
575 }
576
577 private LauncherCallbacks mLauncherCallbacks;
578
579 public void onPostCreate(Bundle savedInstanceState) {
580 super.onPostCreate(savedInstanceState);
581 if (mLauncherCallbacks != null) {
582 mLauncherCallbacks.onPostCreate(savedInstanceState);
583 }
584 }
585
586 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
587 mLauncherCallbacks = callbacks;
588 return true;
589 }
590
591 @Override
592 public void onLauncherProviderChange() {
593 if (mLauncherCallbacks != null) {
594 mLauncherCallbacks.onLauncherProviderChange();
595 }
596 }
597
598 /** To be overridden by subclasses to hint to Launcher that we have custom content */
599 protected boolean hasCustomContentToLeft() {
600 if (mLauncherCallbacks != null) {
601 return mLauncherCallbacks.hasCustomContentToLeft();
602 }
603 return false;
604 }
605
606 /**
607 * To be overridden by subclasses to populate the custom content container and call
608 * {@link #addToCustomContentPage}. This will only be invoked if
609 * {@link #hasCustomContentToLeft()} is {@code true}.
610 */
611 protected void populateCustomContentContainer() {
612 if (mLauncherCallbacks != null) {
613 mLauncherCallbacks.populateCustomContentContainer();
614 }
615 }
616
617 /**
618 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
619 * ensure the custom content page is added or removed if necessary.
620 */
621 protected void invalidateHasCustomContentToLeft() {
622 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
623 // Not bound yet, wait for bindScreens to be called.
624 return;
625 }
626
627 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
628 // Create the custom content page and call the subclass to populate it.
629 mWorkspace.createCustomContentContainer();
630 populateCustomContentContainer();
631 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
632 mWorkspace.removeCustomContentPage();
633 }
634 }
635
636 private void checkForLocaleChange() {
637 if (sLocaleConfiguration == null) {
638 new AsyncTask<Void, Void, LocaleConfiguration>() {
639 @Override
640 protected LocaleConfiguration doInBackground(Void... unused) {
641 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
642 readConfiguration(Launcher.this, localeConfiguration);
643 return localeConfiguration;
644 }
645
646 @Override
647 protected void onPostExecute(LocaleConfiguration result) {
648 sLocaleConfiguration = result;
649 checkForLocaleChange(); // recursive, but now with a locale configuration
650 }
651 }.execute();
652 return;
653 }
654
655 final Configuration configuration = getResources().getConfiguration();
656
657 final String previousLocale = sLocaleConfiguration.locale;
658 final String locale = configuration.locale.toString();
659
660 final int previousMcc = sLocaleConfiguration.mcc;
661 final int mcc = configuration.mcc;
662
663 final int previousMnc = sLocaleConfiguration.mnc;
664 final int mnc = configuration.mnc;
665
666 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
667
668 if (localeChanged) {
669 sLocaleConfiguration.locale = locale;
670 sLocaleConfiguration.mcc = mcc;
671 sLocaleConfiguration.mnc = mnc;
672
673 mIconCache.flush();
674
675 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
676 new AsyncTask<Void, Void, Void>() {
677 public Void doInBackground(Void ... args) {
678 writeConfiguration(Launcher.this, localeConfiguration);
679 return null;
680 }
681 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
682 }
683 }
684
685 private static class LocaleConfiguration {
686 public String locale;
687
688 public int mcc = -1;
689
690 public int mnc = -1;
691 }
692
693 private static void readConfiguration(Context context, LocaleConfiguration configuration) {
694 DataInputStream in = null;
695 try {
696 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
697 configuration.locale = in.readUTF();
698 configuration.mcc = in.readInt();
699 configuration.mnc = in.readInt();
700 } catch (FileNotFoundException e) {
701 // Ignore
702 } catch (IOException e) {
703 // Ignore
704 } finally {
705 if (in != null) {
706 try {
707 in.close();
708 } catch (IOException e) {
709 // Ignore
710 }
711 }
712 }
713 }
714
715 private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
716 DataOutputStream out = null;
717 try {
718 out = new DataOutputStream(context.openFileOutput(
719 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
720 out.writeUTF(configuration.locale);
721 out.writeInt(configuration.mcc);
722 out.writeInt(configuration.mnc);
723 out.flush();
724 } catch (FileNotFoundException e) {
725 // Ignore
726 } catch (IOException e) {
727 //noinspection ResultOfMethodCallIgnored
728 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
729 } finally {
730 if (out != null) {
731 try {
732 out.close();
733 } catch (IOException e) {
734 // Ignore
735 }
736 }
737 }
738 }
739
740 public Stats getStats() {
741 return mStats;
742 }
743
744 public LayoutInflater getInflater() {
745 return mInflater;
746 }
747
748 boolean isDraggingEnabled() {
749 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
750 // that is subsequently removed from the workspace in startBinding().
751 return !mModel.isLoadingWorkspace();
752 }
753
754 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
755 public static int generateViewId() {
756 if (Build.VERSION.SDK_INT >= 17) {
757 return View.generateViewId();
758 } else {
759 // View.generateViewId() is not available. The following fallback logic is a copy
760 // of its implementation.
761 for (;;) {
762 final int result = sNextGeneratedId.get();
763 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
764 int newValue = result + 1;
765 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
766 if (sNextGeneratedId.compareAndSet(result, newValue)) {
767 return result;
768 }
769 }
770 }
771 }
772
773 public int getViewIdForItem(ItemInfo info) {
774 // This cast is safe given the > 2B range for int.
775 int itemId = (int) info.id;
776 if (mItemIdToViewId.containsKey(itemId)) {
777 return mItemIdToViewId.get(itemId);
778 }
779 int viewId = generateViewId();
780 mItemIdToViewId.put(itemId, viewId);
781 return viewId;
782 }
783
784 /**
785 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
786 * a configuration step, this allows the proper animations to run after other transitions.
787 */
788 private long completeAdd(PendingAddArguments args) {
789 long screenId = args.screenId;
790 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
791 // When the screen id represents an actual screen (as opposed to a rank) we make sure
792 // that the drop page actually exists.
793 screenId = ensurePendingDropLayoutExists(args.screenId);
794 }
795
796 switch (args.requestCode) {
797 case REQUEST_CREATE_SHORTCUT:
798 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
799 args.cellY);
800 break;
801 case REQUEST_CREATE_APPWIDGET:
802 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
803 break;
804 case REQUEST_RECONFIGURE_APPWIDGET:
805 completeRestoreAppWidget(args.appWidgetId);
806 break;
807 }
808 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
809 // if you turned the screen off and then back while in All Apps, Launcher would not
810 // return to the workspace. Clearing mAddInfo.container here fixes this issue
811 resetAddInfo();
812 return screenId;
813 }
814
815 private void handleActivityResult(
816 final int requestCode, final int resultCode, final Intent data) {
817 // Reset the startActivity waiting flag
818 setWaitingForResult(false);
819 final int pendingAddWidgetId = mPendingAddWidgetId;
820 mPendingAddWidgetId = -1;
821
822 Runnable exitSpringLoaded = new Runnable() {
823 @Override
824 public void run() {
825 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
826 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
827 }
828 };
829
830 if (requestCode == REQUEST_BIND_APPWIDGET) {
831 final int appWidgetId = data != null ?
832 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
833 if (resultCode == RESULT_CANCELED) {
834 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
835 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
836 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
837 } else if (resultCode == RESULT_OK) {
838 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
839 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
840 }
841 return;
842 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
843 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
844 mWorkspace.exitOverviewMode(false);
845 }
846 return;
847 }
848
849 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
850 requestCode == REQUEST_CREATE_APPWIDGET);
851
852 final boolean workspaceLocked = isWorkspaceLocked();
853 // We have special handling for widgets
854 if (isWidgetDrop) {
855 final int appWidgetId;
856 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
857 : -1;
858 if (widgetId < 0) {
859 appWidgetId = pendingAddWidgetId;
860 } else {
861 appWidgetId = widgetId;
862 }
863
864 final int result;
865 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
866 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
867 "returned from the widget configuration activity.");
868 result = RESULT_CANCELED;
869 completeTwoStageWidgetDrop(result, appWidgetId);
870 final Runnable onComplete = new Runnable() {
871 @Override
872 public void run() {
873 exitSpringLoadedDragModeDelayed(false, 0, null);
874 }
875 };
876 if (workspaceLocked) {
877 // No need to remove the empty screen if we're mid-binding, as the
878 // the bind will not add the empty screen.
879 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
880 } else {
881 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
882 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
883 }
884 } else {
885 if (!workspaceLocked) {
886 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
887 // When the screen id represents an actual screen (as opposed to a rank)
888 // we make sure that the drop page actually exists.
889 mPendingAddInfo.screenId =
890 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
891 }
892 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
893
894 dropLayout.setDropPending(true);
895 final Runnable onComplete = new Runnable() {
896 @Override
897 public void run() {
898 completeTwoStageWidgetDrop(resultCode, appWidgetId);
899 dropLayout.setDropPending(false);
900 }
901 };
902 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
903 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
904 } else {
905 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
906 mPendingAddInfo);
907 sPendingAddItem = args;
908 }
909 }
910 return;
911 }
912
913 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
914 if (resultCode == RESULT_OK) {
915 // Update the widget view.
916 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
917 pendingAddWidgetId, mPendingAddInfo);
918 if (workspaceLocked) {
919 sPendingAddItem = args;
920 } else {
921 completeAdd(args);
922 }
923 }
924 // Leave the widget in the pending state if the user canceled the configure.
925 return;
926 }
927
928 // The pattern used here is that a user PICKs a specific application,
929 // which, depending on the target, might need to CREATE the actual target.
930
931 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
932 // launch over to the Music app to actually CREATE_SHORTCUT.
933 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
934 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
935 mPendingAddInfo);
936 if (isWorkspaceLocked()) {
937 sPendingAddItem = args;
938 } else {
939 completeAdd(args);
940 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
941 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
942 }
943 } else if (resultCode == RESULT_CANCELED) {
944 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
945 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
946 }
947 mDragLayer.clearAnimatedView();
948
949 }
950
951 @Override
952 protected void onActivityResult(
953 final int requestCode, final int resultCode, final Intent data) {
954 handleActivityResult(requestCode, resultCode, data);
955 if (mLauncherCallbacks != null) {
956 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
957 }
958 }
959
960 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
961 appWidgetId, ItemInfo info) {
962 PendingAddArguments args = new PendingAddArguments();
963 args.requestCode = requestCode;
964 args.intent = data;
965 args.container = info.container;
966 args.screenId = info.screenId;
967 args.cellX = info.cellX;
968 args.cellY = info.cellY;
969 args.appWidgetId = appWidgetId;
970 return args;
971 }
972
973 /**
974 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
975 *
976 * @param screenId the screen id to check
977 * @return the new screen, or screenId if it exists
978 */
979 private long ensurePendingDropLayoutExists(long screenId) {
980 CellLayout dropLayout =
981 (CellLayout) mWorkspace.getScreenWithId(screenId);
982 if (dropLayout == null) {
983 // it's possible that the add screen was removed because it was
984 // empty and a re-bind occurred
985 mWorkspace.addExtraEmptyScreen();
986 return mWorkspace.commitExtraEmptyScreen();
987 } else {
988 return screenId;
989 }
990 }
991
992 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
993 CellLayout cellLayout =
994 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
995 Runnable onCompleteRunnable = null;
996 int animationType = 0;
997
998 AppWidgetHostView boundWidget = null;
999 if (resultCode == RESULT_OK) {
1000 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
1001 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
1002 mPendingAddWidgetInfo);
1003 boundWidget = layout;
1004 onCompleteRunnable = new Runnable() {
1005 @Override
1006 public void run() {
1007 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
1008 mPendingAddInfo.screenId, layout, null);
1009 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
1010 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
1011 }
1012 };
1013 } else if (resultCode == RESULT_CANCELED) {
1014 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1015 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
1016 }
1017 if (mDragLayer.getAnimatedView() != null) {
1018 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
1019 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
1020 animationType, boundWidget, true);
1021 } else if (onCompleteRunnable != null) {
1022 // The animated view may be null in the case of a rotation during widget configuration
1023 onCompleteRunnable.run();
1024 }
1025 }
1026
1027 @Override
1028 protected void onStop() {
1029 super.onStop();
1030 FirstFrameAnimatorHelper.setIsVisible(false);
1031
1032 if (mLauncherCallbacks != null) {
1033 mLauncherCallbacks.onStop();
1034 }
1035 }
1036
1037 @Override
1038 protected void onStart() {
1039 super.onStart();
1040 FirstFrameAnimatorHelper.setIsVisible(true);
1041
1042 if (mLauncherCallbacks != null) {
1043 mLauncherCallbacks.onStart();
1044 }
1045 }
1046
1047 @Override
1048 protected void onResume() {
1049 long startTime = 0;
1050 if (DEBUG_RESUME_TIME) {
1051 startTime = System.currentTimeMillis();
1052 Log.v(TAG, "Launcher.onResume()");
1053 }
1054 if (mLauncherCallbacks != null) {
1055 mLauncherCallbacks.preOnResume();
1056 }
1057 super.onResume();
1058 // Restore the previous launcher state
1059 if ((mOnResumeState == State.WORKSPACE) || (mOnResumeState == State.NONE)) {
1060 showWorkspace(false);
1061 } else if (mOnResumeState == State.APPS) {
1062 /* animated */
1063 /* resetListToTop */
1064 showAppsView(false, false);
1065 } else if (mOnResumeState == State.WIDGETS) {
1066 showWidgetsView(false, false);
1067 }
1068 mOnResumeState = State.NONE;
1069 // Background was set to gradient in onPause(), restore to black if in all apps.
1070 setWorkspaceBackground(mState == State.WORKSPACE);
1071 mPaused = false;
1072 if (mRestoring || mOnResumeNeedsLoad) {
1073 setWorkspaceLoading(true);
1074 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1075 mRestoring = false;
1076 mOnResumeNeedsLoad = false;
1077 }
1078 if (mBindOnResumeCallbacks.size() > 0) {
1079 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1080 // execute them here
1081 long startTimeCallbacks = 0;
1082 if (DEBUG_RESUME_TIME) {
1083 startTimeCallbacks = System.currentTimeMillis();
1084 }
1085 if (mAppsCustomizeContent != null) {
1086 mAppsCustomizeContent.setBulkBind(true);
1087 }
1088 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1089 mBindOnResumeCallbacks.get(i).run();
1090 }
1091 if (mAppsCustomizeContent != null) {
1092 mAppsCustomizeContent.setBulkBind(false);
1093 }
1094 mBindOnResumeCallbacks.clear();
1095 if (DEBUG_RESUME_TIME) {
1096 Log.d(TAG, "Time spent processing callbacks in onResume: " + (System.currentTimeMillis() 🔵
1097 }
1098 }
1099 if (mOnResumeCallbacks.size() > 0) {
1100 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1101 mOnResumeCallbacks.get(i).run();
1102 }
1103 mOnResumeCallbacks.clear();
1104 }
1105 // Reset the pressed state of icons that were locked in the press state while activities
1106 // were launching
1107 if (mWaitingForResume != null) {
1108 // Resets the previous workspace icon press state
1109 mWaitingForResume.setStayPressed(false);
1110 }
1111 // It is possible that widgets can receive updates while launcher is not in the foreground.
1112 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1113 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1114 // orientation.
1115 getWorkspace().reinflateWidgetsIfNecessary();
1116 // Process any items that were added while Launcher was away.
1117 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1118 if (DEBUG_RESUME_TIME) {
1119 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1120 }
1121 if (mWorkspace.getCustomContentCallbacks() != null) {
1122 // If we are resuming and the custom content is the current page, we call onShow().
1123 // It is also poassible that onShow will instead be called slightly after first layout
1124 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1125 if (mWorkspace.isOnOrMovingToCustomContent()) {
1126 mWorkspace.getCustomContentCallbacks().onShow(true);
1127 }
1128 }
1129 mWorkspace.updateInteractionForState();
1130 mWorkspace.onResume();
1131 PackageInstallerCompat.getInstance(this).onResume();
1132 if (mLauncherCallbacks != null) {
1133 mLauncherCallbacks.onResume();
1134 }
1135 }
1136
1137 @Override
1138 protected void onPause() {
1139 // Ensure that items added to Launcher are queued until Launcher returns
1140 InstallShortcutReceiver.enableInstallQueue();
1141 PackageInstallerCompat.getInstance(this).onPause();
1142
1143 super.onPause();
1144 mPaused = true;
1145 mDragController.cancelDrag();
1146 mDragController.resetLastGestureUpTime();
1147
1148 // We call onHide() aggressively. The custom content callbacks should be able to
1149 // debounce excess onHide calls.
1150 if (mWorkspace.getCustomContentCallbacks() != null) {
1151 mWorkspace.getCustomContentCallbacks().onHide();
1152 }
1153
1154 if (mLauncherCallbacks != null) {
1155 mLauncherCallbacks.onPause();
1156 }
1157 }
1158
1159 public interface CustomContentCallbacks {
1160 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1161 // by a onResume or by scrolling otherwise.
1162 public abstract void onShow(boolean fromResume);
1163
1164 // Custom content is completely hidden
1165 public abstract void onHide();
1166
1167 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1168 public abstract void onScrollProgressChanged(float progress);
1169
1170 // Indicates whether the user is allowed to scroll away from the custom content.
1171 public abstract boolean isScrollingAllowed();
1172 }
1173
1174 public interface LauncherOverlay {
1175 /**
1176 * Touch interaction leading to overscroll has begun
1177 */
1178 public abstract void onScrollInteractionBegin();
1179
1180 /**
1181 * Touch interaction related to overscroll has ended
1182 */
1183 public abstract void onScrollInteractionEnd();
1184
1185 /**
1186 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1187 * screen (or in the case of RTL, the rightmost screen).
1188 */
1189 public abstract void onScrollChange(int progress, boolean rtl);
1190
1191 /**
1192 * Screen has stopped scrolling
1193 */
1194 public abstract void onScrollSettled();
1195
1196 /**
1197 * This method can be called by the Launcher in order to force the LauncherOverlay
1198 * to exit fully immersive mode.
1199 */
1200 public abstract void forceExitFullImmersion();
1201 }
1202
1203 public interface LauncherOverlayCallbacks {
1204 /**
1205 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1206 * however it doesn't modify any state within the launcher.
1207 */
1208 public abstract boolean canEnterFullImmersion();
1209
1210 /**
1211 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1212 * eg. by occupying the full screen and handling all touch events.
1213 *
1214 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1215 case, Launcher will modify any necessary state and assumes the overlay is
1216 handling all interaction. If false, the LauncherOverlay should cancel any
1217 */
1218 public abstract boolean enterFullImmersion();
1219
1220 /**
1221 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1222 * full control over UI and state.
1223 */
1224 public abstract void exitFullImmersion();
1225 }
1226
1227 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1228 @Override
1229 public boolean canEnterFullImmersion() {
1230 return mState == State.WORKSPACE;
1231 }
1232
1233 @Override
1234 public boolean enterFullImmersion() {
1235 if (mState == State.WORKSPACE) {
1236 // When fully immersed, disregard any touches which fall through.
1237 mDragLayer.setBlockTouch(true);
1238 return true;
1239 }
1240 return false;
1241 }
1242
1243 @Override
1244 public void exitFullImmersion() {
1245 mDragLayer.setBlockTouch(false);
1246 }
1247 }
1248
1249 protected boolean hasSettings() {
1250 if (mLauncherCallbacks != null) {
1251 return mLauncherCallbacks.hasSettings();
1252 }
1253 return false;
1254 }
1255
1256 public void addToCustomContentPage(View customContent,
1257 CustomContentCallbacks callbacks, String description) {
1258 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1259 }
1260
1261 // The custom content needs to offset its content to account for the QSB
1262 public int getTopOffsetForCustomContent() {
1263 return mWorkspace.getPaddingTop();
1264 }
1265
1266 @Override
1267 public Object onRetainNonConfigurationInstance() {
1268 // Flag the loader to stop early before switching
1269 if (mModel.isCurrentCallbacks(this)) {
1270 mModel.stopLoader();
1271 }
1272 if (mAppsCustomizeContent != null) {
1273 mAppsCustomizeContent.surrender();
1274 }
1275 return Boolean.TRUE;
1276 }
1277
1278 // We can't hide the IME if it was forced open. So don't bother
1279 @Override
1280 public void onWindowFocusChanged(boolean hasFocus) {
1281 super.onWindowFocusChanged(hasFocus);
1282 mHasFocus = hasFocus;
1283
1284 if (mLauncherCallbacks != null) {
1285 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1286 }
1287 }
1288
1289 private boolean acceptFilter() {
1290 final InputMethodManager inputManager = (InputMethodManager)
1291 getSystemService(Context.INPUT_METHOD_SERVICE);
1292 return !inputManager.isFullscreenMode();
1293 }
1294
1295 @Override
1296 public boolean onKeyDown(int keyCode, KeyEvent event) {
1297 final int uniChar = event.getUnicodeChar();
1298 final boolean handled = super.onKeyDown(keyCode, event);
1299 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1300 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1301 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1302 keyCode, event);
1303 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1304 // something usable has been typed - start a search
1305 // the typed text will be retrieved and cleared by
1306 // showSearchDialog()
1307 // If there are multiple keystrokes before the search dialog takes focus,
1308 // onSearchRequested() will be called for every keystroke,
1309 // but it is idempotent, so it's fine.
1310 return onSearchRequested();
1311 }
1312 }
1313
1314 // Eat the long press event so the keyboard doesn't come up.
1315 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1316 return true;
1317 }
1318
1319 return handled;
1320 }
1321
1322 private String getTypedText() {
1323 return mDefaultKeySsb.toString();
1324 }
1325
1326 private void clearTypedText() {
1327 mDefaultKeySsb.clear();
1328 mDefaultKeySsb.clearSpans();
1329 Selection.setSelection(mDefaultKeySsb, 0);
1330 }
1331
1332 /**
1333 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1334 * State
1335 */
1336 private static State intToState(int stateOrdinal) {
1337 State state = State.WORKSPACE;
1338 final State[] stateValues = State.values();
1339 for (int i = 0; i < stateValues.length; i++) {
1340 if (stateValues[i].ordinal() == stateOrdinal) {
1341 state = stateValues[i];
1342 break;
1343 }
1344 }
1345 return state;
1346 }
1347
1348 /**
1349 * Restores the previous state, if it exists.
1350 *
1351 * @param savedState The previous state.
1352 */
1353 @SuppressWarnings("unchecked")
1354 private void restoreState(Bundle savedState) {
1355 if (savedState == null) {
1356 return;
1357 }
1358 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1359 if ((state == State.APPS) || (state == State.WIDGETS)) {
1360 mOnResumeState = state;
1361 }
1362 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, PagedView.INVALID_RESTORE_PAG🔵
1363 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1364 mWorkspace.setRestorePage(currentScreen);
1365 }
1366 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1367 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1368 if ((pendingAddContainer != ItemInfo.NO_ID) && (pendingAddScreen > (-1))) {
1369 mPendingAddInfo.container = pendingAddContainer;
1370 mPendingAddInfo.screenId = pendingAddScreen;
1371 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1372 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1373 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1374 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1375 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1376 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1377 setWaitingForResult(true);
1378 mRestoring = true;
1379 }
1380 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1381 if (renameFolder) {
1382 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1383 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1384 mRestoring = true;
1385 }
1386 // Restore the AppsCustomize tab
1387 if (mAppsCustomizeTabHost != null) {
1388 String curTab = savedState.getString("apps_customize_currentTab");
1389 if (curTab != null) {
1390 mAppsCustomizeTabHost.setContentTypeImmediate(mAppsCustomizeTabHost.getContentTypeForTabT🔵
1391 mAppsCustomizeContent.loadAssociatedPages(mAppsCustomizeContent.getCurrentPage());
1392 }
1393 int currentIndex = savedState.getInt("apps_customize_currentIndex");
1394 mAppsCustomizeContent.restorePageForIndex(currentIndex);
1395 }
1396 mItemIdToViewId = ((HashMap<Integer, Integer>) (savedState.getSerializable(RUNTIME_STATE_VIEW_IDS🔵
1397 }
1398
1399 /**
1400 * Finds all the views we need and configure them properly.
1401 */
1402 private void setupViews() {
1403 final DragController dragController = mDragController;
1404 mLauncherView = findViewById(R.id.launcher);
1405 mFocusHandler = ((FocusIndicatorView) (findViewById(R.id.focus_indicator)));
1406 mDragLayer = ((DragLayer) (findViewById(R.id.drag_layer)));
1407 mWorkspace = ((Workspace) (mDragLayer.findViewById(R.id.workspace)));
1408 mWorkspace.setPageSwitchListener(this);
1409 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1410 mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_L🔵
1411 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1412 // Setup the drag layer
1413 mDragLayer.setup(this, dragController);
1414 // Setup the hotseat
1415 mHotseat = ((Hotseat) (findViewById(R.id.hotseat)));
1416 if (mHotseat != null) {
1417 mHotseat.setup(this);
1418 mHotseat.setOnLongClickListener(this);
1419 }
1420 mOverviewPanel = ((ViewGroup) (findViewById(R.id.overview_panel)));
1421 View widgetButton = findViewById(R.id.widget_button);
1422 widgetButton.setOnClickListener(new OnClickListener() {
1423 @Override
1424 public void onClick(View arg0) {
1425 if (!mWorkspace.isSwitchingState()) {
1426 onClickAddWidgetButton(arg0);
1427 }
1428 }
1429 });
1430 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1431 View wallpaperButton = findViewById(R.id.wallpaper_button);
1432 wallpaperButton.setOnClickListener(new OnClickListener() {
1433 @Override
1434 public void onClick(View arg0) {
1435 if (!mWorkspace.isSwitchingState()) {
1436 onClickWallpaperPicker(arg0);
1437 }
1438 }
1439 });
1440 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1441 View settingsButton = findViewById(R.id.settings_button);
1442 if (hasSettings()) {
1443 settingsButton.setOnClickListener(new OnClickListener() {
1444 @Override
1445 public void onClick(View arg0) {
1446 if (!mWorkspace.isSwitchingState()) {
1447 onClickSettingsButton(arg0);
1448 }
1449 }
1450 });
1451 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1452 } else {
1453 settingsButton.setVisibility(View.GONE);
1454 }
1455 mOverviewPanel.setAlpha(0.0F);
1456 // Setup the workspace
1457 mWorkspace.setHapticFeedbackEnabled(false);
1458 mWorkspace.setOnLongClickListener(this);
1459 mWorkspace.setup(dragController);
1460 dragController.addDragListener(mWorkspace);
1461 // Get the search/delete bar
1462 mSearchDropTargetBar = ((SearchDropTargetBar) (mDragLayer.findViewById(R.id.search_drop_target_ba🔵
1463 // Setup Apps
1464 mAppsView = ((AppsContainerView) (findViewById(R.id.apps_view)));
1465 // Setup AppsCustomize
1466 mAppsCustomizeTabHost = ((AppsCustomizeTabHost) (findViewById(R.id.apps_customize_pane)));
1467 mAppsCustomizeContent = ((AppsCustomizePagedView) (mAppsCustomizeTabHost.findViewById(R.id.apps_c🔵
1468 mAppsCustomizeContent.setup(this, dragController);
1469 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1470 dragController.setDragScoller(mWorkspace);
1471 dragController.setScrollView(mDragLayer);
1472 dragController.setMoveTarget(mWorkspace);
1473 dragController.addDropTarget(mWorkspace);
1474 if (mSearchDropTargetBar != null) {
1475 mSearchDropTargetBar.setup(this, dragController);
1476 if (getOrCreateQsbBar() == null) {
1477 // Explicitly set it to null during initialization.
1478 mSearchDropTargetBar.setQsbSearchBar(null);
1479 }
1480 }
1481 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1482 Log.v(TAG, "adding WeightWatcher");
1483 mWeightWatcher = new WeightWatcher(this);
1484 mWeightWatcher.setAlpha(0.5F);
1485 ((FrameLayout) (mLauncherView)).addView(mWeightWatcher, new FrameLayout.LayoutParams(FrameLay🔵
1486 boolean show = shouldShowWeightWatcher();
1487 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1488 }
1489 }
1490
1491 /**
1492 * Sets the all apps button. This method is called from {@link Hotseat}.
1493 */
1494 public void setAllAppsButton(View allAppsButton) {
1495 mAllAppsButton = allAppsButton;
1496 }
1497
1498 public View getAllAppsButton() {
1499 return mAllAppsButton;
1500 }
1501
1502 /**
1503 * Creates a view representing a shortcut.
1504 *
1505 * @param info The data structure describing the shortcut.
1506 *
1507 * @return A View inflated from R.layout.application.
1508 */
1509 View createShortcut(ShortcutInfo info) {
1510 return createShortcut(R.layout.application,
1511 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1512 }
1513
1514 /**
1515 * Creates a view representing a shortcut inflated from the specified resource.
1516 *
1517 * @param layoutResId The id of the XML layout used to create the shortcut.
1518 * @param parent The group the shortcut belongs to.
1519 * @param info The data structure describing the shortcut.
1520 *
1521 * @return A View inflated from layoutResId.
1522 */
1523 public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1524 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1525 favorite.applyFromShortcutInfo(info, mIconCache, true);
1526 favorite.setOnClickListener(this);
1527 favorite.setOnFocusChangeListener(mFocusHandler);
1528 return favorite;
1529 }
1530
1531 /**
1532 * Add a shortcut to the workspace.
1533 *
1534 * @param data The intent describing the shortcut.
1535 * @param cellInfo The position on screen where to create the shortcut.
1536 */
1537 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1538 int cellY) {
1539 int[] cellXY = mTmpAddItemCellCoordinates;
1540 int[] touchXY = mPendingAddInfo.dropPos;
1541 CellLayout layout = getCellLayout(container, screenId);
1542
1543 boolean foundCellSpan = false;
1544
1545 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
1546 if (info == null) {
1547 return;
1548 }
1549 final View view = createShortcut(info);
1550
1551 // First we check if we already know the exact location where we want to add this item.
1552 if (cellX >= 0 && cellY >= 0) {
1553 cellXY[0] = cellX;
1554 cellXY[1] = cellY;
1555 foundCellSpan = true;
1556
1557 // If appropriate, either create a folder or add to an existing folder
1558 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1559 true, null,null)) {
1560 return;
1561 }
1562 DragObject dragObject = new DragObject();
1563 dragObject.dragInfo = info;
1564 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1565 true)) {
1566 return;
1567 }
1568 } else if (touchXY != null) {
1569 // when dragging and dropping, just find the closest free spot
1570 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1571 foundCellSpan = (result != null);
1572 } else {
1573 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1574 }
1575
1576 if (!foundCellSpan) {
1577 showOutOfSpaceMessage(isHotseatLayout(layout));
1578 return;
1579 }
1580
1581 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
1582
1583 if (!mRestoring) {
1584 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1585 isWorkspaceLocked());
1586 }
1587 }
1588
1589 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1590 int minHeight) {
1591 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1592 // We want to account for the extra amount of padding that we are adding to the widget
1593 // to ensure that it gets the full amount of space that it has requested
1594 int requiredWidth = minWidth + padding.left + padding.right;
1595 int requiredHeight = minHeight + padding.top + padding.bottom;
1596 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1597 }
1598
1599 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1600 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1601 }
1602
1603 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1604 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1605 }
1606
1607 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1608 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1609 }
1610
1611 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1612 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1613 info.minResizeHeight);
1614 }
1615
1616 /**
1617 * Add a widget to the workspace.
1618 *
1619 * @param appWidgetId The app widget id
1620 */
1621 private void completeAddAppWidget(int appWidgetId, long container, long screenId,
1622 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1623
1624 ItemInfo info = mPendingAddInfo;
1625 if (appWidgetInfo == null) {
1626 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1627 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1628 }
1629
1630 if (appWidgetInfo.isCustomWidget) {
1631 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1632 }
1633
1634 LauncherAppWidgetInfo launcherInfo;
1635 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1636 launcherInfo.spanX = info.spanX;
1637 launcherInfo.spanY = info.spanY;
1638 launcherInfo.minSpanX = info.minSpanX;
1639 launcherInfo.minSpanY = info.minSpanY;
1640 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1641
1642 LauncherModel.addItemToDatabase(this, launcherInfo,
1643 container, screenId, info.cellX, info.cellY, false);
1644
1645 if (!mRestoring) {
1646 if (hostView == null) {
1647 // Perform actual inflation because we're live
1648 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1649 appWidgetInfo);
1650 } else {
1651 // The AppWidgetHostView has already been inflated and instantiated
1652 launcherInfo.hostView = hostView;
1653 }
1654 launcherInfo.hostView.setTag(launcherInfo);
1655 launcherInfo.hostView.setVisibility(View.VISIBLE);
1656 launcherInfo.notifyWidgetSizeChanged(this);
1657
1658 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1659 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1660
1661 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1662 }
1663 resetAddInfo();
1664 }
1665
1666 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1667 @Override
1668 public void onReceive(Context context, Intent intent) {
1669 final String action = intent.getAction();
1670 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1671 mUserPresent = false;
1672 mDragLayer.clearAllResizeFrames();
1673 updateAutoAdvanceState();
1674 // Reset AllApps to its initial state only if we are not in the middle of
1675 // processing a multi-step drop
1676 if (((mAppsView != null) && (mAppsCustomizeTabHost != null)) && (mPendingAddInfo.containe🔵
1677 showWorkspace(false);
1678 }
1679 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1680 mUserPresent = true;
1681 updateAutoAdvanceState();
1682 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1683 mModel.resetLoadedState(false, true);
1684 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR🔵
1685 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1686 mModel.resetLoadedState(false, true);
1687 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR🔵
1688 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) || LauncherAppsComp🔵
1689 getModel().forceReload();
1690 }
1691 }
1692 };
1693
1694 @Override
1695 public void onAttachedToWindow() {
1696 super.onAttachedToWindow();
1697
1698 // Listen for broadcasts related to user-presence
1699 final IntentFilter filter = new IntentFilter();
1700 filter.addAction(Intent.ACTION_SCREEN_OFF);
1701 filter.addAction(Intent.ACTION_USER_PRESENT);
1702 // For handling managed profiles
1703 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1704 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1705 if (ENABLE_DEBUG_INTENTS) {
1706 filter.addAction(DebugIntents.DELETE_DATABASE);
1707 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1708 }
1709 registerReceiver(mReceiver, filter);
1710 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1711 setupTransparentSystemBarsForLmp();
1712 mAttached = true;
1713 mVisible = true;
1714 }
1715
1716 /**
1717 * Sets up transparent navigation and status bars in LMP.
1718 * This method is a no-op for other platform versions.
1719 */
1720 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1721 private void setupTransparentSystemBarsForLmp() {
1722 if (Utilities.isLmpOrAbove()) {
1723 Window window = getWindow();
1724 window.getAttributes().systemUiVisibility |= (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM🔵
1725 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutPa🔵
1726 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1727 window.setStatusBarColor(Color.TRANSPARENT);
1728 window.setNavigationBarColor(Color.TRANSPARENT);
1729 }
1730 }
1731
1732 @Override
1733 public void onDetachedFromWindow() {
1734 super.onDetachedFromWindow();
1735 mVisible = false;
1736 if (mAttached) {
1737 unregisterReceiver(mReceiver);
1738 mAttached = false;
1739 }
1740 updateAutoAdvanceState();
1741 }
1742
1743 public void onWindowVisibilityChanged(int visibility) {
1744 mVisible = visibility == View.VISIBLE;
1745 updateAutoAdvanceState();
1746 // The following code used to be in onResume, but it turns out onResume is called when
1747 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1748 // is a more appropriate event to handle
1749 if (mVisible) {
1750 mAppsCustomizeTabHost.onWindowVisible();
1751 if (!mWorkspaceLoading) {
1752 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1753 // We want to let Launcher draw itself at least once before we force it to build
1754 // layers on all the workspace pages, so that transitioning to Launcher from other
1755 // apps is nice and speedy.
1756 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1757 private boolean mStarted = false;
1758
1759 public void onDraw() {
1760 if (mStarted) {
1761 return;
1762 }
1763 mStarted = true;
1764 // We delay the layer building a bit in order to give
1765 // other message processing a time to run. In particular
1766 // this avoids a delay in hiding the IME if it was
1767 // currently shown, because doing that may involve
1768 // some communication back with the app.
1769 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1770 final ViewTreeObserver.OnDrawListener listener = this;
1771 mWorkspace.post(new Runnable() {
1772 public void run() {
1773 if ((mWorkspace != null) && (mWorkspace.getViewTreeObserver() != null)) {
1774 mWorkspace.getViewTreeObserver().removeOnDrawListener(listener);
1775 }
1776 }
1777 });
1778 return;
1779 }
1780 });
1781 }
1782 clearTypedText();
1783 }
1784 }
1785
1786 private void sendAdvanceMessage(long delay) {
1787 mHandler.removeMessages(ADVANCE_MSG);
1788 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1789 mHandler.sendMessageDelayed(msg, delay);
1790 mAutoAdvanceSentTime = System.currentTimeMillis();
1791 }
1792
1793 private void updateAutoAdvanceState() {
1794 boolean autoAdvanceRunning = (mVisible && mUserPresent) && (!mWidgetsToAdvance.isEmpty());
1795 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1796 mAutoAdvanceRunning = autoAdvanceRunning;
1797 if (autoAdvanceRunning) {
1798 long delay = (mAutoAdvanceTimeLeft == (-1)) ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1799 sendAdvanceMessage(delay);
1800 } else {
1801 if (!mWidgetsToAdvance.isEmpty()) {
1802 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - (System.currentTimeMillis() - m🔵
1803 }
1804 mHandler.removeMessages(ADVANCE_MSG);
1805 mHandler.removeMessages(0);// Remove messages sent using postDelayed()
1806
1807 }
1808 }
1809 }
1810
1811 private final Handler mHandler = new Handler() {
1812 @Override
1813 public void handleMessage(Message msg) {
1814 if (msg.what == ADVANCE_MSG) {
1815 int i = 0;
1816 for (View key: mWidgetsToAdvance.keySet()) {
1817 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1818 final int delay = mAdvanceStagger * i;
1819 if (v instanceof Advanceable) {
1820 postDelayed(new Runnable() {
1821 public void run() {
1822 ((Advanceable) v).advance();
1823 }
1824 }, delay);
1825 }
1826 i++;
1827 }
1828 sendAdvanceMessage(mAdvanceInterval);
1829 }
1830 }
1831 };
1832
1833 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1834 if ((appWidgetInfo == null) || (appWidgetInfo.autoAdvanceViewId == (-1))) {
1835 return;
1836 }
1837 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1838 if (v instanceof Advanceable) {
1839 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1840 ((Advanceable) (v)).fyiWillBeAdvancedByHostKThx();
1841 updateAutoAdvanceState();
1842 }
1843 }
1844
1845 void removeWidgetToAutoAdvance(View hostView) {
1846 if (mWidgetsToAdvance.containsKey(hostView)) {
1847 mWidgetsToAdvance.remove(hostView);
1848 updateAutoAdvanceState();
1849 }
1850 }
1851
1852 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1853 removeWidgetToAutoAdvance(launcherInfo.hostView);
1854 launcherInfo.hostView = null;
1855 }
1856
1857 void showOutOfSpaceMessage(boolean isHotseatLayout) {
1858 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1859 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1860 }
1861
1862 public DragLayer getDragLayer() {
1863 return mDragLayer;
1864 }
1865
1866 public AppsContainerView getAppsView() {
1867 return mAppsView;
1868 }
1869
1870 public AppsCustomizeTabHost getWidgetsView() {
1871 return mAppsCustomizeTabHost;
1872 }
1873
1874 public Workspace getWorkspace() {
1875 return mWorkspace;
1876 }
1877
1878 public Hotseat getHotseat() {
1879 return mHotseat;
1880 }
1881
1882 public ViewGroup getOverviewPanel() {
1883 return mOverviewPanel;
1884 }
1885
1886 public SearchDropTargetBar getSearchBar() {
1887 return mSearchDropTargetBar;
1888 }
1889
1890 public LauncherAppWidgetHost getAppWidgetHost() {
1891 return mAppWidgetHost;
1892 }
1893
1894 public LauncherModel getModel() {
1895 return mModel;
1896 }
1897
1898 protected SharedPreferences getSharedPrefs() {
1899 return mSharedPrefs;
1900 }
1901
1902 public void closeSystemDialogs() {
1903 getWindow().closeAllPanels();
1904
1905 // Whatever we were doing is hereby canceled.
1906 setWaitingForResult(false);
1907 }
1908
1909 @Override
1910 protected void onNewIntent(Intent intent) {
1911 long startTime = 0;
1912 if (DEBUG_RESUME_TIME) {
1913 startTime = System.currentTimeMillis();
1914 }
1915 super.onNewIntent(intent);
1916 // Close the menu
1917 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1918 // also will cancel mWaitingForResult.
1919 closeSystemDialogs();
1920 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT🔵
1921 if (mWorkspace == null) {
1922 // Can be cases where mWorkspace is null, this prevents a NPE
1923 return;
1924 }
1925 Folder openFolder = mWorkspace.getOpenFolder();
1926 // In all these cases, only animate if we're already on home
1927 mWorkspace.exitWidgetResizeMode();
1928 boolean moveToDefaultScreen = (mLauncherCallbacks != null) ? mLauncherCallbacks.shouldMoveToD🔵
1929 if ((((alreadyOnHome && (mState == State.WORKSPACE)) && (!mWorkspace.isTouchActive())) && (op🔵
1930 mWorkspace.moveToDefaultScreen(true);
1931 }
1932 closeFolder();
1933 exitSpringLoadedDragMode();
1934 // If we are already on home, then just animate back to the workspace,
1935 // otherwise, just wait until onResume to set the state back to Workspace
1936 if (alreadyOnHome) {
1937 showWorkspace(true);
1938 } else {
1939 mOnResumeState = State.WORKSPACE;
1940 }
1941 final View v = getWindow().peekDecorView();
1942 if ((v != null) && (v.getWindowToken() != null)) {
1943 InputMethodManager imm = ((InputMethodManager) (getSystemService(INPUT_METHOD_SERVICE)));
1944 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1945 }
1946 // Reset the apps view
1947 if ((!alreadyOnHome) && (mAppsView != null)) {
1948 mAppsView.scrollToTop();
1949 }
1950 // Reset the apps customize page
1951 if ((!alreadyOnHome) && (mAppsCustomizeTabHost != null)) {
1952 mAppsCustomizeTabHost.reset();
1953 }
1954 if (mLauncherCallbacks != null) {
1955 mLauncherCallbacks.onHomeIntent();
1956 }
1957 }
1958 if (DEBUG_RESUME_TIME) {
1959 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1960 }
1961 if (mLauncherCallbacks != null) {
1962 mLauncherCallbacks.onNewIntent(intent);
1963 }
1964 }
1965
1966 @Override
1967 public void onRestoreInstanceState(Bundle state) {
1968 super.onRestoreInstanceState(state);
1969 for (int page: mSynchronouslyBoundPages) {
1970 mWorkspace.restoreInstanceStateForChild(page);
1971 }
1972 }
1973
1974 @Override
1975 protected void onSaveInstanceState(Bundle outState) {
1976 if (mWorkspace.getChildCount() > 0) {
1977 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1978 mWorkspace.getCurrentPageOffsetFromCustomContent());
1979 }
1980 super.onSaveInstanceState(outState);
1981
1982 outState.putInt(RUNTIME_STATE, mState.ordinal());
1983 // We close any open folder since it will not be re-opened, and we need to make sure
1984 // this state is reflected.
1985 closeFolder();
1986
1987 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
1988 mWaitingForResult) {
1989 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1990 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1991 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1992 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1993 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1994 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1995 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1996 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1997 }
1998
1999 if (mFolderInfo != null && mWaitingForResult) {
2000 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
2001 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
2002 }
2003
2004 // Save the current AppsCustomize tab
2005 if (mAppsCustomizeTabHost != null) {
2006 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
2007 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
2008 if (currentTabTag != null) {
2009 outState.putString("apps_customize_currentTab", currentTabTag);
2010 }
2011 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
2012 outState.putInt("apps_customize_currentIndex", currentIndex);
2013 }
2014 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2015
2016 if (mLauncherCallbacks != null) {
2017 mLauncherCallbacks.onSaveInstanceState(outState);
2018 }
2019 }
2020
2021 @Override
2022 public void onDestroy() {
2023 super.onDestroy();
2024
2025 // Remove all pending runnables
2026 mHandler.removeMessages(ADVANCE_MSG);
2027 mHandler.removeMessages(0);
2028 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2029
2030 // Stop callbacks from LauncherModel
2031 LauncherAppState app = (LauncherAppState.getInstance());
2032
2033 // It's possible to receive onDestroy after a new Launcher activity has
2034 // been created. In this case, don't interfere with the new Launcher.
2035 if (mModel.isCurrentCallbacks(this)) {
2036 mModel.stopLoader();
2037 app.setLauncher(null);
2038 }
2039
2040 try {
2041 mAppWidgetHost.stopListening();
2042 } catch (NullPointerException ex) {
2043 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2044 }
2045 mAppWidgetHost = null;
2046
2047 mWidgetsToAdvance.clear();
2048
2049 TextKeyListener.getInstance().release();
2050
2051 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2052 // to prevent leaking Launcher activities on orientation change.
2053 if (mModel != null) {
2054 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2055 }
2056
2057 getContentResolver().unregisterContentObserver(mWidgetObserver);
2058 unregisterReceiver(mCloseSystemDialogsReceiver);
2059
2060 mDragLayer.clearAllResizeFrames();
2061 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2062 mWorkspace.removeAllWorkspaceScreens();
2063 mWorkspace = null;
2064 mDragController = null;
2065
2066 LauncherAnimUtils.onDestroyActivity();
2067
2068 if (mLauncherCallbacks != null) {
2069 mLauncherCallbacks.onDestroy();
2070 }
2071 }
2072
2073 public DragController getDragController() {
2074 return mDragController;
2075 }
2076
2077 @Override
2078 public void startActivityForResult(Intent intent, int requestCode) {
2079 if (requestCode >= 0) {
2080 setWaitingForResult(true);
2081 }
2082 super.startActivityForResult(intent, requestCode);
2083 }
2084
2085 /**
2086 * Indicates that we want global search for this activity by setting the globalSearch
2087 * argument for {@link #startSearch} to true.
2088 */
2089 @Override
2090 public void startSearch(String initialQuery, boolean selectInitialQuery,
2091 Bundle appSearchData, boolean globalSearch) {
2092
2093 showWorkspace(true);
2094
2095 if (initialQuery == null) {
2096 // Use any text typed in the launcher as the initial query
2097 initialQuery = getTypedText();
2098 }
2099 if (appSearchData == null) {
2100 appSearchData = new Bundle();
2101 appSearchData.putString("source", "launcher-search");
2102 }
2103 Rect sourceBounds = new Rect();
2104 if (mSearchDropTargetBar != null) {
2105 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2106 }
2107
2108 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2109 appSearchData, sourceBounds);
2110 if (clearTextImmediately) {
2111 clearTypedText();
2112 }
2113 }
2114
2115 /**
2116 * Start a text search.
2117 *
2118 * @return {@code true} if the search will start immediately, so any further keypresses
2119 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2120 * to buffer keypresses.
2121 */
2122 public boolean startSearch(String initialQuery,
2123 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2124 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2125 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2126 sourceBounds);
2127 }
2128
2129 startGlobalSearch(initialQuery, selectInitialQuery,
2130 appSearchData, sourceBounds);
2131 return false;
2132 }
2133
2134 /**
2135 * Starts the global search activity. This code is a copied from SearchManager
2136 */
2137 private void startGlobalSearch(String initialQuery,
2138 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2139 final SearchManager searchManager =
2140 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2141 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2142 if (globalSearchActivity == null) {
2143 Log.w(TAG, "No global search activity found.");
2144 return;
2145 }
2146 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2147 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2148 intent.setComponent(globalSearchActivity);
2149 // Make sure that we have a Bundle to put source in
2150 if (appSearchData == null) {
2151 appSearchData = new Bundle();
2152 } else {
2153 appSearchData = new Bundle(appSearchData);
2154 }
2155 // Set source to package name of app that starts global search if not set already.
2156 if (!appSearchData.containsKey("source")) {
2157 appSearchData.putString("source", getPackageName());
2158 }
2159 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2160 if (!TextUtils.isEmpty(initialQuery)) {
2161 intent.putExtra(SearchManager.QUERY, initialQuery);
2162 }
2163 if (selectInitialQuery) {
2164 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2165 }
2166 intent.setSourceBounds(sourceBounds);
2167 try {
2168 startActivity(intent);
2169 } catch (ActivityNotFoundException ex) {
2170 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2171 }
2172 }
2173
2174 public boolean isOnCustomContent() {
2175 return mWorkspace.isOnOrMovingToCustomContent();
2176 }
2177
2178 @Override
2179 public boolean onPrepareOptionsMenu(Menu menu) {
2180 super.onPrepareOptionsMenu(menu);
2181 if (!isOnCustomContent()) {
2182 // Close any open folders
2183 closeFolder();
2184 // Stop resizing any widgets
2185 mWorkspace.exitWidgetResizeMode();
2186 if (!mWorkspace.isInOverviewMode()) {
2187 // Show the overview mode
2188 showOverviewMode(true);
2189 } else {
2190 showWorkspace(true);
2191 }
2192 }
2193 if (mLauncherCallbacks != null) {
2194 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2195 }
2196
2197 return false;
2198 }
2199
2200 @Override
2201 public boolean onSearchRequested() {
2202 startSearch(null, false, null, true);
2203 // Use a custom animation for launching search
2204 return true;
2205 }
2206
2207 public boolean isWorkspaceLocked() {
2208 return mWorkspaceLoading || mWaitingForResult;
2209 }
2210
2211 public boolean isWorkspaceLoading() {
2212 return mWorkspaceLoading;
2213 }
2214
2215 private void setWorkspaceLoading(boolean value) {
2216 boolean isLocked = isWorkspaceLocked();
2217 mWorkspaceLoading = value;
2218 if (isLocked != isWorkspaceLocked()) {
2219 onWorkspaceLockedChanged();
2220 }
2221 }
2222
2223 private void setWaitingForResult(boolean value) {
2224 boolean isLocked = isWorkspaceLocked();
2225 mWaitingForResult = value;
2226 if (isLocked != isWorkspaceLocked()) {
2227 onWorkspaceLockedChanged();
2228 }
2229 }
2230
2231 protected void onWorkspaceLockedChanged() {
2232 if (mLauncherCallbacks != null) {
2233 mLauncherCallbacks.onWorkspaceLockedChanged();
2234 }
2235 }
2236
2237 private void resetAddInfo() {
2238 mPendingAddInfo.container = ItemInfo.NO_ID;
2239 mPendingAddInfo.screenId = -1;
2240 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2241 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2242 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2243 mPendingAddInfo.dropPos = null;
2244 }
2245
2246 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2247 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2248 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2249 }
2250
2251 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2252 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2253 int delay) {
2254 if (appWidgetInfo.configure != null) {
2255 mPendingAddWidgetInfo = appWidgetInfo;
2256 mPendingAddWidgetId = appWidgetId;
2257
2258 // Launch over to configure widget, if needed
2259 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2260 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2261
2262 } else {
2263 // Otherwise just add it
2264 Runnable onComplete = new Runnable() {
2265 @Override
2266 public void run() {
2267 // Exit spring loaded mode if necessary after adding the widget
2268 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2269 null);
2270 }
2271 };
2272 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2273 appWidgetInfo);
2274 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2275 }
2276 }
2277
2278 protected void moveToCustomContentScreen(boolean animate) {
2279 // Close any folders that may be open.
2280 closeFolder();
2281 mWorkspace.moveToCustomContentScreen(animate);
2282 }
2283
2284 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2285 int[] cell, int spanX, int spanY) {
2286 switch (info.itemType) {
2287 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2288 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2289 int span[] = new int[2];
2290 span[0] = spanX;
2291 span[1] = spanY;
2292 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2293 container, screenId, cell, span);
2294 break;
2295 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2296 processShortcutFromDrop(info.componentName, container, screenId, cell);
2297 break;
2298 default:
2299 throw new IllegalStateException("Unknown item type: " + info.itemType);
2300 }
2301 }
2302
2303 /**
2304 * Process a shortcut drop.
2305 *
2306 * @param componentName The name of the component
2307 * @param screenId The ID of the screen where it should be added
2308 * @param cell The cell it should be added to, optional
2309 */
2310 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2311 int[] cell) {
2312 resetAddInfo();
2313 mPendingAddInfo.container = container;
2314 mPendingAddInfo.screenId = screenId;
2315 mPendingAddInfo.dropPos = null;
2316
2317 if (cell != null) {
2318 mPendingAddInfo.cellX = cell[0];
2319 mPendingAddInfo.cellY = cell[1];
2320 }
2321
2322 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2323 createShortcutIntent.setComponent(componentName);
2324 processShortcut(createShortcutIntent);
2325 }
2326
2327 /**
2328 * Process a widget drop.
2329 *
2330 * @param info The PendingAppWidgetInfo of the widget being added.
2331 * @param screenId The ID of the screen where it should be added
2332 * @param cell The cell it should be added to, optional
2333 */
2334 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2335 int[] cell, int[] span) {
2336 resetAddInfo();
2337 mPendingAddInfo.container = info.container = container;
2338 mPendingAddInfo.screenId = info.screenId = screenId;
2339 mPendingAddInfo.dropPos = null;
2340 mPendingAddInfo.minSpanX = info.minSpanX;
2341 mPendingAddInfo.minSpanY = info.minSpanY;
2342
2343 if (cell != null) {
2344 mPendingAddInfo.cellX = cell[0];
2345 mPendingAddInfo.cellY = cell[1];
2346 }
2347 if (span != null) {
2348 mPendingAddInfo.spanX = span[0];
2349 mPendingAddInfo.spanY = span[1];
2350 }
2351
2352 AppWidgetHostView hostView = info.boundWidget;
2353 int appWidgetId;
2354 if (hostView != null) {
2355 appWidgetId = hostView.getAppWidgetId();
2356 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2357 } else {
2358 // In this case, we either need to start an activity to get permission to bind
2359 // the widget, or we need to start an activity to configure the widget, or both.
2360 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2361 Bundle options = info.bindOptions;
2362
2363 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2364 appWidgetId, info.info, options);
2365 if (success) {
2366 addAppWidgetImpl(appWidgetId, info, null, info.info);
2367 } else {
2368 mPendingAddWidgetInfo = info.info;
2369 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2370 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2371 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2372 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2373 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2374 // TODO: we need to make sure that this accounts for the options bundle.
2375 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2376 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2377 }
2378 }
2379 }
2380
2381 void processShortcut(Intent intent) {
2382 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2383 }
2384
2385 void processWallpaper(Intent intent) {
2386 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2387 }
2388
2389 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2390 int cellY) {
2391 final FolderInfo folderInfo = new FolderInfo();
2392 folderInfo.title = getText(R.string.folder_name);
2393
2394 // Update the model
2395 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
2396 false);
2397 sFolders.put(folderInfo.id, folderInfo);
2398
2399 // Create the view
2400 FolderIcon newFolder =
2401 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2402 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2403 isWorkspaceLocked());
2404 // Force measure the new folder icon
2405 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2406 parent.getShortcutsAndWidgets().measureChild(newFolder);
2407 return newFolder;
2408 }
2409
2410 void removeFolder(FolderInfo folder) {
2411 sFolders.remove(folder.id);
2412 }
2413
2414 protected ComponentName getWallpaperPickerComponent() {
2415 if (mLauncherCallbacks != null) {
2416 return mLauncherCallbacks.getWallpaperPickerComponent();
2417 }
2418 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
2419 }
2420
2421 /**
2422 * Registers various content observers. The current implementation registers
2423 * only a favorites observer to keep track of the favorites applications.
2424 */
2425 private void registerContentObservers() {
2426 ContentResolver resolver = getContentResolver();
2427 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2428 true, mWidgetObserver);
2429 }
2430
2431 @Override
2432 public boolean dispatchKeyEvent(KeyEvent event) {
2433 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2434 switch (event.getKeyCode()) {
2435 case KeyEvent.KEYCODE_HOME:
2436 return true;
2437 case KeyEvent.KEYCODE_VOLUME_DOWN:
2438 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2439 dumpState();
2440 return true;
2441 }
2442 break;
2443 }
2444 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2445 switch (event.getKeyCode()) {
2446 case KeyEvent.KEYCODE_HOME:
2447 return true;
2448 }
2449 }
2450
2451 return super.dispatchKeyEvent(event);
2452 }
2453
2454 @Override
2455 public void onBackPressed() {
2456 if ((mLauncherCallbacks != null) && mLauncherCallbacks.handleBackPressed()) {
2457 return;
2458 }
2459 if (LauncherAppState.getInstance().getAccessibilityDelegate().onBackPressed()) {
2460 return;
2461 }
2462 if (isAppsViewVisible()) {
2463 showWorkspace(true);
2464 } else if (isWidgetsViewVisible()) {
2465 showOverviewMode(true);
2466 } else if (mWorkspace.isInOverviewMode()) {
2467 mWorkspace.exitOverviewMode(true);
2468 } else if (mWorkspace.getOpenFolder() != null) {
2469 Folder openFolder = mWorkspace.getOpenFolder();
2470 if (openFolder.isEditingName()) {
2471 openFolder.dismissEditingName();
2472 } else {
2473 closeFolder();
2474 }
2475 } else {
2476 mWorkspace.exitWidgetResizeMode();
2477 // Back button is a no-op here, but give at least some feedback for the button press
2478 mWorkspace.showOutlinesTemporarily();
2479 }
2480 }
2481
2482 /**
2483 * Re-listen when widgets are reset.
2484 */
2485 private void onAppWidgetReset() {
2486 if (mAppWidgetHost != null) {
2487 mAppWidgetHost.startListening();
2488 }
2489 }
2490
2491 /**
2492 * Launches the intent referred by the clicked shortcut.
2493 *
2494 * @param v The view representing the clicked shortcut.
2495 */
2496 public void onClick(View v) {
2497 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2498 // view has detached (it's possible for this to happen if the view is removed mid touch).
2499 if (v.getWindowToken() == null) {
2500 return;
2501 }
2502
2503 if (!mWorkspace.isFinishedSwitchingState()) {
2504 return;
2505 }
2506
2507 if (v instanceof Workspace) {
2508 if (mWorkspace.isInOverviewMode()) {
2509 mWorkspace.exitOverviewMode(true);
2510 }
2511 return;
2512 }
2513
2514 if (v instanceof CellLayout) {
2515 if (mWorkspace.isInOverviewMode()) {
2516 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2517 }
2518 }
2519
2520 Object tag = v.getTag();
2521 if (tag instanceof ShortcutInfo) {
2522 onClickAppShortcut(v);
2523 } else if (tag instanceof FolderInfo) {
2524 if (v instanceof FolderIcon) {
2525 onClickFolderIcon(v);
2526 }
2527 } else if (v == mAllAppsButton) {
2528 onClickAllAppsButton(v);
2529 } else if (tag instanceof AppInfo) {
2530 startAppShortcutOrInfoActivity(v);
2531 } else if (tag instanceof LauncherAppWidgetInfo) {
2532 if (v instanceof PendingAppWidgetHostView) {
2533 onClickPendingWidget((PendingAppWidgetHostView) v);
2534 }
2535 }
2536 }
2537
2538 public void onClickPagedViewIcon(View v) {
2539 startAppShortcutOrInfoActivity(v);
2540 if (mLauncherCallbacks != null) {
2541 mLauncherCallbacks.onClickPagedViewIcon(v);
2542 }
2543 }
2544
2545 public boolean onTouch(View v, MotionEvent event) {
2546 return false;
2547 }
2548
2549 /**
2550 * Event handler for the app widget view which has not fully restored.
2551 */
2552 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2553 if (mIsSafeModeEnabled) {
2554 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2555 return;
2556 }
2557
2558 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2559 if (v.isReadyForClickSetup()) {
2560 int widgetId = info.appWidgetId;
2561 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2562 if (appWidgetInfo != null) {
2563 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2564 this, appWidgetInfo);
2565 mPendingAddInfo.copyFrom(info);
2566 mPendingAddWidgetId = widgetId;
2567
2568 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2569 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2570 }
2571 } else if (info.installProgress < 0) {
2572 // The install has not been queued
2573 final String packageName = info.providerName.getPackageName();
2574 showBrokenAppInstallDialog(packageName,
2575 new DialogInterface.OnClickListener() {
2576 public void onClick(DialogInterface dialog, int id) {
2577 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2578 }
2579 });
2580 } else {
2581 // Download has started.
2582 final String packageName = info.providerName.getPackageName();
2583 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2584 }
2585 }
2586
2587 /**
2588 * Event handler for the "grid" button that appears on the home screen, which
2589 * enters all apps mode.
2590 *
2591 * @param v The view that was clicked.
2592 */
2593 protected void onClickAllAppsButton(View v) {
2594 if (LOGD) {
2595 Log.d(TAG, "onClickAllAppsButton");
2596 }
2597 if (isAppsViewVisible()) {
2598 showWorkspace(true);
2599 } else {
2600 /* animated */
2601 /* resetListToTop */
2602 showAppsView(true, false);
2603 }
2604 if (mLauncherCallbacks != null) {
2605 mLauncherCallbacks.onClickAllAppsButton(v);
2606 }
2607 }
2608
2609 private void showBrokenAppInstallDialog(final String packageName,
2610 DialogInterface.OnClickListener onSearchClickListener) {
2611 new AlertDialog.Builder(this)
2612 .setTitle(R.string.abandoned_promises_title)
2613 .setMessage(R.string.abandoned_promise_explanation)
2614 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2615 .setNeutralButton(R.string.abandoned_clean_this,
2616 new DialogInterface.OnClickListener() {
2617 public void onClick(DialogInterface dialog, int id) {
2618 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2619 mWorkspace.removeAbandonedPromise(packageName, user);
2620 }
2621 })
2622 .create().show();
2623 return;
2624 }
2625
2626 /**
2627 * Event handler for an app shortcut click.
2628 *
2629 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2630 */
2631 protected void onClickAppShortcut(final View v) {
2632 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2633 Object tag = v.getTag();
2634 if (!(tag instanceof ShortcutInfo)) {
2635 throw new IllegalArgumentException("Input must be a Shortcut");
2636 }
2637
2638 // Open shortcut
2639 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2640
2641 if (shortcut.isDisabled != 0) {
2642 int error = R.string.activity_not_available;
2643 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2644 error = R.string.safemode_shortcut_error;
2645 }
2646 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2647 return;
2648 }
2649
2650 final Intent intent = shortcut.intent;
2651
2652 // Check for special shortcuts
2653 if (intent.getComponent() != null) {
2654 final String shortcutClass = intent.getComponent().getClassName();
2655
2656 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2657 MemoryDumpActivity.startDump(this);
2658 return;
2659 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2660 toggleShowWeightWatcher();
2661 return;
2662 }
2663 }
2664
2665 // Check for abandoned promise
2666 if ((v instanceof BubbleTextView)
2667 && shortcut.isPromise()
2668 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2669 showBrokenAppInstallDialog(
2670 shortcut.getTargetComponent().getPackageName(),
2671 new DialogInterface.OnClickListener() {
2672 public void onClick(DialogInterface dialog, int id) {
2673 startAppShortcutOrInfoActivity(v);
2674 }
2675 });
2676 return;
2677 }
2678
2679 // Start activities
2680 startAppShortcutOrInfoActivity(v);
2681
2682 if (mLauncherCallbacks != null) {
2683 mLauncherCallbacks.onClickAppShortcut(v);
2684 }
2685 }
2686
2687 private void startAppShortcutOrInfoActivity(View v) {
2688 Object tag = v.getTag();
2689 final ShortcutInfo shortcut;
2690 final Intent intent;
2691 if (tag instanceof ShortcutInfo) {
2692 shortcut = (ShortcutInfo) tag;
2693 intent = shortcut.intent;
2694 int[] pos = new int[2];
2695 v.getLocationOnScreen(pos);
2696 intent.setSourceBounds(new Rect(pos[0], pos[1],
2697 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2698
2699 } else if (tag instanceof AppInfo) {
2700 shortcut = null;
2701 intent = ((AppInfo) tag).intent;
2702 } else {
2703 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2704 }
2705
2706 boolean success = startActivitySafely(v, intent, tag);
2707 mStats.recordLaunch(intent, shortcut);
2708
2709 if (success && v instanceof BubbleTextView) {
2710 mWaitingForResume = (BubbleTextView) v;
2711 mWaitingForResume.setStayPressed(true);
2712 }
2713 }
2714
2715 /**
2716 * Event handler for a folder icon click.
2717 *
2718 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2719 */
2720 protected void onClickFolderIcon(View v) {
2721 if (LOGD) Log.d(TAG, "onClickFolder");
2722 if (!(v instanceof FolderIcon)){
2723 throw new IllegalArgumentException("Input must be a FolderIcon");
2724 }
2725
2726 FolderIcon folderIcon = (FolderIcon) v;
2727 final FolderInfo info = folderIcon.getFolderInfo();
2728 Folder openFolder = mWorkspace.getFolderForTag(info);
2729
2730 // If the folder info reports that the associated folder is open, then verify that
2731 // it is actually opened. There have been a few instances where this gets out of sync.
2732 if (info.opened && openFolder == null) {
2733 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2734 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2735 info.opened = false;
2736 }
2737
2738 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2739 // Close any open folder
2740 closeFolder();
2741 // Open the requested folder
2742 openFolder(folderIcon);
2743 } else {
2744 // Find the open folder...
2745 int folderScreen;
2746 if (openFolder != null) {
2747 folderScreen = mWorkspace.getPageForView(openFolder);
2748 // .. and close it
2749 closeFolder(openFolder);
2750 if (folderScreen != mWorkspace.getCurrentPage()) {
2751 // Close any folder open on the current screen
2752 closeFolder();
2753 // Pull the folder onto this screen
2754 openFolder(folderIcon);
2755 }
2756 }
2757 }
2758
2759 if (mLauncherCallbacks != null) {
2760 mLauncherCallbacks.onClickFolderIcon(v);
2761 }
2762 }
2763
2764 /**
2765 * Event handler for the (Add) Widgets button that appears after a long press
2766 * on the home screen.
2767 */
2768 protected void onClickAddWidgetButton(View view) {
2769 if (LOGD) {
2770 Log.d(TAG, "onClickAddWidgetButton");
2771 }
2772 if (mIsSafeModeEnabled) {
2773 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2774 } else {
2775 /* animated */
2776 /* resetPageToZero */
2777 showWidgetsView(true, true);
2778 if (mLauncherCallbacks != null) {
2779 mLauncherCallbacks.onClickAddWidgetButton(view);
2780 }
2781 }
2782 }
2783
2784 /**
2785 * Event handler for the wallpaper picker button that appears after a long press
2786 * on the home screen.
2787 */
2788 protected void onClickWallpaperPicker(View v) {
2789 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2790 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
2791 pickWallpaper.setComponent(getWallpaperPickerComponent());
2792 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
2793
2794 if (mLauncherCallbacks != null) {
2795 mLauncherCallbacks.onClickWallpaperPicker(v);
2796 }
2797 }
2798
2799 /**
2800 * Event handler for a click on the settings button that appears after a long press
2801 * on the home screen.
2802 */
2803 protected void onClickSettingsButton(View v) {
2804 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2805 if (mLauncherCallbacks != null) {
2806 mLauncherCallbacks.onClickSettingsButton(v);
2807 }
2808 }
2809
2810 public void onTouchDownAllAppsButton(View v) {
2811 // Provide the same haptic feedback that the system offers for virtual keys.
2812 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2813 }
2814
2815 public void performHapticFeedbackOnTouchDown(View v) {
2816 // Provide the same haptic feedback that the system offers for virtual keys.
2817 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2818 }
2819
2820 public View.OnTouchListener getHapticFeedbackTouchListener() {
2821 if (mHapticFeedbackTouchListener == null) {
2822 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2823 @Override
2824 public boolean onTouch(View v, MotionEvent event) {
2825 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2826 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2827 }
2828 return false;
2829 }
2830 };
2831 }
2832 return mHapticFeedbackTouchListener;
2833 }
2834
2835 public void onDragStarted(View view) {
2836 if (isOnCustomContent()) {
2837 // Custom content screen doesn't participate in drag and drop. If on custom
2838 // content screen, move to default.
2839 moveWorkspaceToDefaultScreen();
2840 }
2841
2842 if (mLauncherCallbacks != null) {
2843 mLauncherCallbacks.onDragStarted(view);
2844 }
2845 }
2846
2847 /**
2848 * Called when the user stops interacting with the launcher.
2849 * This implies that the user is now on the homescreen and is not doing housekeeping.
2850 */
2851 protected void onInteractionEnd() {
2852 if (mLauncherCallbacks != null) {
2853 mLauncherCallbacks.onInteractionEnd();
2854 }
2855 }
2856
2857 /**
2858 * Called when the user starts interacting with the launcher.
2859 * The possible interactions are:
2860 * - open all apps
2861 * - reorder an app shortcut, or a widget
2862 * - open the overview mode.
2863 * This is a good time to stop doing things that only make sense
2864 * when the user is on the homescreen and not doing housekeeping.
2865 */
2866 protected void onInteractionBegin() {
2867 if (mLauncherCallbacks != null) {
2868 mLauncherCallbacks.onInteractionBegin();
2869 }
2870 }
2871
2872 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2873 try {
2874 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2875 launcherApps.showAppDetailsForProfile(componentName, user);
2876 } catch (SecurityException e) {
2877 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2878 Log.e(TAG, "Launcher does not have permission to launch settings");
2879 } catch (ActivityNotFoundException e) {
2880 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2881 Log.e(TAG, "Unable to launch settings");
2882 }
2883 }
2884
2885 // returns true if the activity was started
2886 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2887 UserHandleCompat user) {
2888 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2889 // System applications cannot be installed. For now, show a toast explaining that.
2890 // We may give them the option of disabling apps this way.
2891 int messageId = R.string.uninstall_system_app_text;
2892 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2893 return false;
2894 } else {
2895 String packageName = componentName.getPackageName();
2896 String className = componentName.getClassName();
2897 Intent intent = new Intent(
2898 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2899 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2900 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2901 if (user != null) {
2902 user.addToIntent(intent, Intent.EXTRA_USER);
2903 }
2904 startActivity(intent);
2905 return true;
2906 }
2907 }
2908
2909 boolean startActivity(View v, Intent intent, Object tag) {
2910 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2911 try {
2912 // Only launch using the new animation if the shortcut has not opted out (this is a
2913 // private contract between launcher and may be ignored in the future).
2914 boolean useLaunchAnimation = (v != null) && (!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIM🔵
2915 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2916 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2917 UserHandleCompat user = null;
2918 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2919 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2920 user = userManager.getUserForSerialNumber(serialNumber);
2921 }
2922 Bundle optsBundle = null;
2923 if (useLaunchAnimation) {
2924 ActivityOptions opts = null;
2925 if (sClipRevealMethod != null) {
2926 // TODO: call method directly when Launcher3 can depend on M APIs
2927 int left = 0;
2928 int top = 0;
2929 int width = v.getMeasuredWidth();
2930 int height = v.getMeasuredHeight();
2931 if (v instanceof TextView) {
2932 // Launch from center of icon, not entire view
2933 Drawable icon = Workspace.getTextViewIcon(((TextView) (v)));
2934 if (icon != null) {
2935 Rect bounds = icon.getBounds();
2936 left = (width - bounds.width()) / 2;
2937 top = v.getPaddingTop();
2938 width = bounds.width();
2939 height = bounds.height();
2940 }
2941 }
2942 try {
2943 opts = ((ActivityOptions) (sClipRevealMethod.invoke(null, v, left, top, width, he🔵
2944 } catch (java.lang.IllegalAccessException e) {
2945 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2946 sClipRevealMethod = null;
2947 } catch (InvocationTargetException e) {
2948 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2949 sClipRevealMethod = null;
2950 }
2951 }
2952 if (opts == null) {
2953 opts = (Utilities.isLmpOrAbove()) ? ActivityOptions.makeCustomAnimation(this, R.anim.🔵
2954 }
2955 optsBundle = opts.toBundle();
2956 }
2957 if ((user == null) || user.equals(UserHandleCompat.myUserHandle())) {
2958 // Could be launching some bookkeeping activity
2959 startActivity(intent, optsBundle);
2960 } else {
2961 // TODO Component can be null when shortcuts are supported for secondary user
2962 launcherApps.startActivityForProfile(intent.getComponent(), user, intent.getSourceBounds(🔵
2963 }
2964 return true;
2965 } catch (java.lang.SecurityException e) {
2966 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2967 Log.e(TAG, (((((("Launcher does not have the permission to launch " + intent) + ". Make sure 🔵
2968 }
2969 return false;
2970 }
2971
2972 boolean startActivitySafely(View v, Intent intent, Object tag) {
2973 boolean success = false;
2974 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2975 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2976 return false;
2977 }
2978 try {
2979 success = startActivity(v, intent, tag);
2980 } catch (ActivityNotFoundException e) {
2981 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2982 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2983 }
2984 return success;
2985 }
2986
2987 /**
2988 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2989 * in the DragLayer in the exact absolute location of the original FolderIcon.
2990 */
2991 private void copyFolderIconToImage(FolderIcon fi) {
2992 final int width = fi.getMeasuredWidth();
2993 final int height = fi.getMeasuredHeight();
2994
2995 // Lazy load ImageView, Bitmap and Canvas
2996 if (mFolderIconImageView == null) {
2997 mFolderIconImageView = new ImageView(this);
2998 }
2999 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3000 mFolderIconBitmap.getHeight() != height) {
3001 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3002 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3003 }
3004
3005 DragLayer.LayoutParams lp;
3006 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3007 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3008 } else {
3009 lp = new DragLayer.LayoutParams(width, height);
3010 }
3011
3012 // The layout from which the folder is being opened may be scaled, adjust the starting
3013 // view size by this scale factor.
3014 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3015 lp.customPosition = true;
3016 lp.x = mRectForFolderAnimation.left;
3017 lp.y = mRectForFolderAnimation.top;
3018 lp.width = (int) (scale * width);
3019 lp.height = (int) (scale * height);
3020
3021 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3022 fi.draw(mFolderIconCanvas);
3023 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3024 if (fi.getFolder() != null) {
3025 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3026 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3027 }
3028 // Just in case this image view is still in the drag layer from a previous animation,
3029 // we remove it and re-add it.
3030 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3031 mDragLayer.removeView(mFolderIconImageView);
3032 }
3033 mDragLayer.addView(mFolderIconImageView, lp);
3034 if (fi.getFolder() != null) {
3035 fi.getFolder().bringToFront();
3036 }
3037 }
3038
3039 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3040 if (fi == null) return;
3041 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3042 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3043 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3044
3045 FolderInfo info = (FolderInfo) fi.getTag();
3046 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3047 CellLayout cl = (CellLayout) fi.getParent().getParent();
3048 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3049 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3050 }
3051
3052 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3053 copyFolderIconToImage(fi);
3054 fi.setVisibility(View.INVISIBLE);
3055
3056 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3057 scaleX, scaleY);
3058 if (Utilities.isLmpOrAbove()) {
3059 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3060 }
3061 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3062 oa.start();
3063 }
3064
3065 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3066 if (fi == null) return;
3067 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3068 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3069 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3070
3071 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3072
3073 // We remove and re-draw the FolderIcon in-case it has changed
3074 mDragLayer.removeView(mFolderIconImageView);
3075 copyFolderIconToImage(fi);
3076 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3077 scaleX, scaleY);
3078 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3079 oa.addListener(new AnimatorListenerAdapter() {
3080 @Override
3081 public void onAnimationEnd(Animator animation) {
3082 if (cl != null) {
3083 cl.clearFolderLeaveBehind();
3084 // Remove the ImageView copy of the FolderIcon and make the original visible.
3085 mDragLayer.removeView(mFolderIconImageView);
3086 fi.setVisibility(View.VISIBLE);
3087 }
3088 }
3089 });
3090 oa.start();
3091 }
3092
3093 /**
3094 * Opens the user folder described by the specified tag. The opening of the folder
3095 * is animated relative to the specified View. If the View is null, no animation
3096 * is played.
3097 *
3098 * @param folderInfo The FolderInfo describing the folder to open.
3099 */
3100 public void openFolder(FolderIcon folderIcon) {
3101 Folder folder = folderIcon.getFolder();
3102 FolderInfo info = folder.mInfo;
3103
3104 info.opened = true;
3105
3106 // Just verify that the folder hasn't already been added to the DragLayer.
3107 // There was a one-off crash where the folder had a parent already.
3108 if (folder.getParent() == null) {
3109 mDragLayer.addView(folder);
3110 mDragController.addDropTarget((DropTarget) folder);
3111 } else {
3112 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3113 folder.getParent() + ").");
3114 }
3115 folder.animateOpen();
3116 growAndFadeOutFolderIcon(folderIcon);
3117
3118 // Notify the accessibility manager that this folder "window" has appeared and occluded
3119 // the workspace items
3120 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3121 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3122 }
3123
3124 public void closeFolder() {
3125 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3126 if (folder != null) {
3127 if (folder.isEditingName()) {
3128 folder.dismissEditingName();
3129 }
3130 closeFolder(folder);
3131 }
3132 }
3133
3134 void closeFolder(Folder folder) {
3135 folder.getInfo().opened = false;
3136
3137 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3138 if (parent != null) {
3139 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3140 shrinkAndFadeInFolderIcon(fi);
3141 }
3142 folder.animateClosed();
3143
3144 // Notify the accessibility manager that this folder "window" has disappeard and no
3145 // longer occludeds the workspace items
3146 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3147 }
3148
3149 public boolean onLongClick(View v) {
3150 if (!isDraggingEnabled()) return false;
3151 if (isWorkspaceLocked()) return false;
3152 if (mState != State.WORKSPACE) return false;
3153
3154 if (v instanceof Workspace) {
3155 if (!mWorkspace.isInOverviewMode()) {
3156 if (mWorkspace.enterOverviewMode()) {
3157 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3158 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3159 return true;
3160 } else {
3161 return false;
3162 }
3163 } else {
3164 return false;
3165 }
3166 }
3167
3168 CellLayout.CellInfo longClickCellInfo = null;
3169 View itemUnderLongClick = null;
3170 if (v.getTag() instanceof ItemInfo) {
3171 ItemInfo info = (ItemInfo) v.getTag();
3172 longClickCellInfo = new CellLayout.CellInfo(v, info);;
3173 itemUnderLongClick = longClickCellInfo.cell;
3174 resetAddInfo();
3175 }
3176
3177 // The hotseat touch handling does not go through Workspace, and we always allow long press
3178 // on hotseat items.
3179 final boolean inHotseat = isHotseatLayout(v);
3180 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3181 if (allowLongPress && !mDragController.isDragging()) {
3182 if (itemUnderLongClick == null) {
3183 // User long pressed on empty space
3184 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3185 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3186 if (mWorkspace.isInOverviewMode()) {
3187 mWorkspace.startReordering(v);
3188 } else {
3189 mWorkspace.enterOverviewMode();
3190 }
3191 } else {
3192 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3193 mHotseat.getOrderInHotseat(
3194 longClickCellInfo.cellX,
3195 longClickCellInfo.cellY));
3196 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3197 // User long pressed on an item
3198 mWorkspace.startDrag(longClickCellInfo);
3199 }
3200 }
3201 }
3202 return true;
3203 }
3204
3205 boolean isHotseatLayout(View layout) {
3206 return mHotseat != null && layout != null &&
3207 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3208 }
3209
3210 /**
3211 * Returns the CellLayout of the specified container at the specified screen.
3212 */
3213 public CellLayout getCellLayout(long container, long screenId) {
3214 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3215 if (mHotseat != null) {
3216 return mHotseat.getLayout();
3217 } else {
3218 return null;
3219 }
3220 } else {
3221 return mWorkspace.getScreenWithId(screenId);
3222 }
3223 }
3224
3225 /**
3226 * For overridden classes.
3227 */
3228 public boolean isAllAppsVisible() {
3229 return isAppsViewVisible();
3230 }
3231
3232 public boolean isAppsViewVisible() {
3233 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3234 }
3235
3236 public boolean isWidgetsViewVisible() {
3237 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3238 }
3239
3240 private void setWorkspaceBackground(boolean workspace) {
3241 mLauncherView.setBackground(workspace ?
3242 mWorkspaceBackgroundDrawable : null);
3243 }
3244
3245 protected void changeWallpaperVisiblity(boolean visible) {
3246 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3247 int curflags = getWindow().getAttributes().flags
3248 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3249 if (wpflags != curflags) {
3250 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3251 }
3252 setWorkspaceBackground(visible);
3253 }
3254
3255 @Override
3256 public void onTrimMemory(int level) {
3257 super.onTrimMemory(level);
3258 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3259 // The widget preview db can result in holding onto over
3260 // 3MB of memory for caching which isn't necessary.
3261 SQLiteDatabase.releaseMemory();
3262
3263 // This clears all widget bitmaps from the widget tray
3264 if (mAppsCustomizeTabHost != null) {
3265 mAppsCustomizeTabHost.trimMemory();
3266 }
3267 }
3268 }
3269
3270 @Override
3271 public void onStateTransitionHideSearchBar() {
3272 // Hide the search bar
3273 if (mSearchDropTargetBar != null) {
3274 /* animated */
3275 mSearchDropTargetBar.hideSearchBar(false);
3276 }
3277 }
3278
3279 protected void showWorkspace(boolean animated) {
3280 showWorkspace(animated, null);
3281 }
3282
3283 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3284 if ((mState != State.WORKSPACE) || (mWorkspace.getState() != Workspace.State.NORMAL)) {
3285 boolean wasInSpringLoadedMode = mState != State.WORKSPACE;
3286 mWorkspace.setVisibility(View.VISIBLE);
3287 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL, animated,🔵
3288 // Show the search bar (only animate if we were showing the drop target bar in spring
3289 // loaded mode)
3290 if (mSearchDropTargetBar != null) {
3291 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3292 }
3293 // Set focus to the AppsCustomize button
3294 if (mAllAppsButton != null) {
3295 mAllAppsButton.requestFocus();
3296 }
3297 }
3298 // Change the state *after* we've called all the transition code
3299 mState = State.WORKSPACE;
3300 // Resume the auto-advance of widgets
3301 mUserPresent = true;
3302 updateAutoAdvanceState();
3303 // Send an accessibility event to announce the context change
3304 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3305 onWorkspaceShown(animated);
3306 }
3307
3308 void showOverviewMode(boolean animated) {
3309 mWorkspace.setVisibility(View.VISIBLE);
3310 /* onCompleteRunnable */
3311 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW, animated, n🔵
3312 mState = State.WORKSPACE;
3313 onWorkspaceShown(animated);
3314 }
3315
3316 public void onWorkspaceShown(boolean animated) {
3317 }
3318
3319 /**
3320 * Shows the apps view.
3321 */
3322 void showAppsView(boolean animated, boolean resetListToTop) {
3323 if (resetListToTop) {
3324 mAppsView.scrollToTop();
3325 }
3326 showAppsOrWidgets(animated, State.APPS);
3327 }
3328
3329 /**
3330 * Shows the widgets view.
3331 */
3332 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3333 if (resetPageToZero) {
3334 mAppsCustomizeTabHost.reset();
3335 }
3336 showAppsOrWidgets(animated, State.WIDGETS);
3337 mAppsCustomizeTabHost.post(new Runnable() {
3338 @Override
3339 public void run() {
3340 // We post this in-case the all apps view isn't yet constructed.
3341 mAppsCustomizeTabHost.requestFocus();
3342 }
3343 });
3344 }
3345
3346 /**
3347 * Sets up the transition to show the apps/widgets view.
3348 */
3349 private void showAppsOrWidgets(boolean animated, State toState) {
3350 if (mState != State.WORKSPACE) {
3351 return;
3352 }
3353 if ((toState != State.APPS) && (toState != State.WIDGETS)) {
3354 return;
3355 }
3356 if (toState == State.APPS) {
3357 mStateTransitionAnimation.startAnimationToAllApps(animated);
3358 } else {
3359 mStateTransitionAnimation.startAnimationToWidgets(animated);
3360 }
3361 // Change the state *after* we've called all the transition code
3362 mState = toState;
3363 // Pause the auto-advance of widgets until we are out of AllApps
3364 mUserPresent = false;
3365 updateAutoAdvanceState();
3366 closeFolder();
3367 // Send an accessibility event to announce the context change
3368 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3369 }
3370
3371 void enterSpringLoadedDragMode() {
3372 if (((mState == State.WORKSPACE) || (mState == State.APPS_SPRING_LOADED)) || (mState == State.WID🔵
3373 return;
3374 }
3375 /* animated */
3376 /* onCompleteRunnable */
3377 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED, true, 🔵
3378 mState = (isAppsViewVisible()) ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3379 }
3380
3381 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, final Runnable onComple🔵
3382 if ((mState != State.APPS_SPRING_LOADED) && (mState != State.WIDGETS_SPRING_LOADED)) {
3383 return;
3384 }
3385 mHandler.postDelayed(new Runnable() {
3386 @Override
3387 public void run() {
3388 if (successfulDrop) {
3389 // Before we show workspace, hide all apps again because
3390 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3391 // clean up our state transition functions
3392 mAppsCustomizeTabHost.setVisibility(View.GONE);
3393 showWorkspace(true, onCompleteRunnable);
3394 } else {
3395 exitSpringLoadedDragMode();
3396 }
3397 }
3398 }, delay);
3399 }
3400
3401 void exitSpringLoadedDragMode() {
3402 if (mState == State.APPS_SPRING_LOADED) {
3403 /* animated */
3404 mStateTransitionAnimation.startAnimationToAllApps(true);
3405 mState = State.APPS;
3406 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3407 /* animated */
3408 mStateTransitionAnimation.startAnimationToWidgets(true);
3409 mState = State.WIDGETS;
3410 }
3411 // Otherwise, we are not in spring loaded mode, so don't do anything.
3412 }
3413
3414 void lockAllApps() {
3415 // TODO
3416 }
3417
3418 void unlockAllApps() {
3419 // TODO
3420 }
3421
3422 protected void disableVoiceButtonProxy(boolean disable) {
3423 // NO-OP
3424 }
3425
3426 public View getOrCreateQsbBar() {
3427 if ((mLauncherCallbacks != null) && mLauncherCallbacks.providesSearch()) {
3428 return mLauncherCallbacks.getQsbBar();
3429 }
3430 if (mQsb == null) {
3431 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3432 if (searchProvider == null) {
3433 return null;
3434 }
3435 Bundle opts = new Bundle();
3436 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, AppWidgetProviderInfo.WIDGET_CAT🔵
3437 SharedPreferences sp = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), MODE_🔵
3438 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3439 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3440 if (((!searchProvider.provider.flattenToString().equals(sp.getString(QSB_WIDGET_PROVIDER, nul🔵
3441 // A valid widget is not already bound.
3442 if (widgetId > (-1)) {
3443 mAppWidgetHost.deleteAppWidgetId(widgetId);
3444 widgetId = -1;
3445 }
3446 // Try to bind a new widget
3447 widgetId = mAppWidgetHost.allocateAppWidgetId();
3448 if (!AppWidgetManagerCompat.getInstance(this).bindAppWidgetIdIfAllowed(widgetId, searchPr🔵
3449 mAppWidgetHost.deleteAppWidgetId(widgetId);
3450 widgetId = -1;
3451 }
3452 sp.edit().putInt(QSB_WIDGET_ID, widgetId).putString(QSB_WIDGET_PROVIDER, searchProvider.p🔵
3453 }
3454 if (widgetId != (-1)) {
3455 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3456 mQsb.updateAppWidgetOptions(opts);
3457 mQsb.setPadding(0, 0, 0, 0);
3458 mSearchDropTargetBar.addView(mQsb);
3459 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3460 }
3461 }
3462 return mQsb;
3463 }
3464
3465 @Override
3466 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3467 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3468 final List<CharSequence> text = event.getText();
3469 text.clear();
3470 // Populate event with a fake title based on the current state.
3471 if (mState == State.APPS) {
3472 text.add("Apps");
3473 } else if (mState == State.WIDGETS) {
3474 text.add("Widgets");
3475 } else {
3476 text.add(getString(R.string.all_apps_home_button_label));
3477 }
3478 return result;
3479 }
3480
3481 /**
3482 * Receives notifications when system dialogs are to be closed.
3483 */
3484 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3485 @Override
3486 public void onReceive(Context context, Intent intent) {
3487 closeSystemDialogs();
3488 }
3489 }
3490
3491 /**
3492 * Receives notifications whenever the appwidgets are reset.
3493 */
3494 private class AppWidgetResetObserver extends ContentObserver {
3495 public AppWidgetResetObserver() {
3496 super(new Handler());
3497 }
3498
3499 @Override
3500 public void onChange(boolean selfChange) {
3501 onAppWidgetReset();
3502 }
3503 }
3504
3505 /**
3506 * If the activity is currently paused, signal that we need to run the passed Runnable
3507 * in onResume.
3508 *
3509 * This needs to be called from incoming places where resources might have been loaded
3510 * while we are paused. That is becaues the Configuration might be wrong
3511 * when we're not running, and if it comes back to what it was when we
3512 * were paused, we are not restarted.
3513 *
3514 * Implementation of the method from LauncherModel.Callbacks.
3515 *
3516 * @return true if we are currently paused. The caller might be able to
3517 * skip some work in that case since we will come back again.
3518 */
3519 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3520 if (mPaused) {
3521 Log.i(TAG, "Deferring update until onResume");
3522 if (deletePreviousRunnables) {
3523 while (mBindOnResumeCallbacks.remove(run)) {
3524 }
3525 }
3526 mBindOnResumeCallbacks.add(run);
3527 return true;
3528 } else {
3529 return false;
3530 }
3531 }
3532
3533 private boolean waitUntilResume(Runnable run) {
3534 return waitUntilResume(run, false);
3535 }
3536
3537 public void addOnResumeCallback(Runnable run) {
3538 mOnResumeCallbacks.add(run);
3539 }
3540
3541 /**
3542 * If the activity is currently paused, signal that we need to re-run the loader
3543 * in onResume.
3544 *
3545 * This needs to be called from incoming places where resources might have been loaded
3546 * while we are paused. That is becaues the Configuration might be wrong
3547 * when we're not running, and if it comes back to what it was when we
3548 * were paused, we are not restarted.
3549 *
3550 * Implementation of the method from LauncherModel.Callbacks.
3551 *
3552 * @return true if we are currently paused. The caller might be able to
3553 * skip some work in that case since we will come back again.
3554 */
3555 public boolean setLoadOnResume() {
3556 if (mPaused) {
3557 Log.i(TAG, "setLoadOnResume");
3558 mOnResumeNeedsLoad = true;
3559 return true;
3560 } else {
3561 return false;
3562 }
3563 }
3564
3565 /**
3566 * Implementation of the method from LauncherModel.Callbacks.
3567 */
3568 public int getCurrentWorkspaceScreen() {
3569 if (mWorkspace != null) {
3570 return mWorkspace.getCurrentPage();
3571 } else {
3572 return SCREEN_COUNT / 2;
3573 }
3574 }
3575
3576 /**
3577 * Refreshes the shortcuts shown on the workspace.
3578 *
3579 * Implementation of the method from LauncherModel.Callbacks.
3580 */
3581 public void startBinding() {
3582 setWorkspaceLoading(true);
3583
3584 // If we're starting binding all over again, clear any bind calls we'd postponed in
3585 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3586 // from scratch again
3587 mBindOnResumeCallbacks.clear();
3588
3589 // Clear the workspace because it's going to be rebound
3590 mWorkspace.clearDropTargets();
3591 mWorkspace.removeAllWorkspaceScreens();
3592
3593 mWidgetsToAdvance.clear();
3594 if (mHotseat != null) {
3595 mHotseat.resetLayout();
3596 }
3597 }
3598
3599 @Override
3600 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3601 bindAddScreens(orderedScreenIds);
3602
3603 // If there are no screens, we need to have an empty screen
3604 if (orderedScreenIds.size() == 0) {
3605 mWorkspace.addExtraEmptyScreen();
3606 }
3607
3608 // Create the custom content page (this call updates mDefaultScreen which calls
3609 // setCurrentPage() so ensure that all pages are added before calling this).
3610 if (hasCustomContentToLeft()) {
3611 mWorkspace.createCustomContentContainer();
3612 populateCustomContentContainer();
3613 }
3614 }
3615
3616 @Override
3617 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3618 // Log to disk
3619 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3620 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3621 TextUtils.join(", ", orderedScreenIds), true);
3622 int count = orderedScreenIds.size();
3623 for (int i = 0; i < count; i++) {
3624 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3625 }
3626 }
3627
3628 @Override
3629 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
3630 final long screenId, final int[] cell, final int spanX, final int spanY) {
3631 showWorkspace(true, new Runnable() {
3632
3633 @Override
3634 public void run() {
3635 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
3636 addPendingItem(info, container, screenId, cell, spanX, spanY);
3637 }
3638 });
3639 }
3640
3641 private boolean shouldShowWeightWatcher() {
3642 String spKey = LauncherAppState.getSharedPreferencesKey();
3643 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3644 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3645
3646 return show;
3647 }
3648
3649 private void toggleShowWeightWatcher() {
3650 String spKey = LauncherAppState.getSharedPreferencesKey();
3651 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3652 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3653
3654 show = !show;
3655
3656 SharedPreferences.Editor editor = sp.edit();
3657 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3658 editor.commit();
3659
3660 if (mWeightWatcher != null) {
3661 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3662 }
3663 }
3664
3665 public void bindAppsAdded(final ArrayList<Long> newScreens, final ArrayList<ItemInfo> addNotAnimated,🔵
3666 Runnable r = new Runnable() {
3667 public void run() {
3668 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3669 }
3670 };
3671 if (waitUntilResume(r)) {
3672 return;
3673 }
3674 // Add the new screens
3675 if (newScreens != null) {
3676 bindAddScreens(newScreens);
3677 }
3678 // We add the items without animation on non-visible pages, and with
3679 // animations on the new page (which we will try and snap to).
3680 if ((addNotAnimated != null) && (!addNotAnimated.isEmpty())) {
3681 bindItems(addNotAnimated, 0, addNotAnimated.size(), false);
3682 }
3683 if ((addAnimated != null) && (!addAnimated.isEmpty())) {
3684 bindItems(addAnimated, 0, addAnimated.size(), true);
3685 }
3686 // Remove the extra empty screen
3687 mWorkspace.removeExtraEmptyScreen(false, false);
3688 if ((addedApps != null) && (mAppsView != null)) {
3689 mAppsView.addApps(addedApps);
3690 }
3691 }
3692
3693 /**
3694 * Bind the items start-end from the list.
3695 *
3696 * Implementation of the method from LauncherModel.Callbacks.
3697 */
3698 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3699 final boolean forceAnimateIcons) {
3700 Runnable r = new Runnable() {
3701 public void run() {
3702 bindItems(shortcuts, start, end, forceAnimateIcons);
3703 }
3704 };
3705 if (waitUntilResume(r)) {
3706 return;
3707 }
3708
3709 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3710 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3711 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3712 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3713 Workspace workspace = mWorkspace;
3714 long newShortcutsScreenId = -1;
3715 for (int i = start; i < end; i++) {
3716 final ItemInfo item = shortcuts.get(i);
3717
3718 // Short circuit if we are loading dock items for a configuration which has no dock
3719 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3720 mHotseat == null) {
3721 continue;
3722 }
3723
3724 switch (item.itemType) {
3725 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3726 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3727 ShortcutInfo info = (ShortcutInfo) item;
3728 View shortcut = createShortcut(info);
3729
3730 /*
3731 * TODO: FIX collision case
3732 */
3733 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3734 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3735 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3736 View v = cl.getChildAt(item.cellX, item.cellY);
3737 Object tag = v.getTag();
3738 String desc = "Collision while binding workspace item: " + item
3739 + ". Collides with " + tag;
3740 if (LauncherAppState.isDogfoodBuild()) {
3741 throw (new RuntimeException(desc));
3742 } else {
3743 Log.d(TAG, desc);
3744 }
3745 }
3746 }
3747
3748 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3749 item.cellY, 1, 1);
3750 if (animateIcons) {
3751 // Animate all the applications up now
3752 shortcut.setAlpha(0f);
3753 shortcut.setScaleX(0f);
3754 shortcut.setScaleY(0f);
3755 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3756 newShortcutsScreenId = item.screenId;
3757 }
3758 break;
3759 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3760 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3761 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3762 (FolderInfo) item, mIconCache);
3763 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3764 item.cellY, 1, 1);
3765 break;
3766 default:
3767 throw new RuntimeException("Invalid Item Type");
3768 }
3769 }
3770
3771 if (animateIcons) {
3772 // Animate to the correct page
3773 if (newShortcutsScreenId > -1) {
3774 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3775 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3776 final Runnable startBounceAnimRunnable = new Runnable() {
3777 public void run() {
3778 anim.playTogether(bounceAnims);
3779 anim.start();
3780 }
3781 };
3782 if (newShortcutsScreenId != currentScreenId) {
3783 // We post the animation slightly delayed to prevent slowdowns
3784 // when we are loading right after we return to launcher.
3785 mWorkspace.postDelayed(new Runnable() {
3786 public void run() {
3787 if (mWorkspace != null) {
3788 mWorkspace.snapToPage(newScreenIndex);
3789 mWorkspace.postDelayed(startBounceAnimRunnable,
3790 NEW_APPS_ANIMATION_DELAY);
3791 }
3792 }
3793 }, NEW_APPS_PAGE_MOVE_DELAY);
3794 } else {
3795 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3796 }
3797 }
3798 }
3799 workspace.requestLayout();
3800 }
3801
3802 /**
3803 * Implementation of the method from LauncherModel.Callbacks.
3804 */
3805 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3806 Runnable r = new Runnable() {
3807 public void run() {
3808 bindFolders(folders);
3809 }
3810 };
3811 if (waitUntilResume(r)) {
3812 return;
3813 }
3814 sFolders.clear();
3815 sFolders.putAll(folders);
3816 }
3817
3818 /**
3819 * Add the views for a widget to the workspace.
3820 *
3821 * Implementation of the method from LauncherModel.Callbacks.
3822 */
3823 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3824 Runnable r = new Runnable() {
3825 public void run() {
3826 bindAppWidget(item);
3827 }
3828 };
3829 if (waitUntilResume(r)) {
3830 return;
3831 }
3832
3833 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3834 if (DEBUG_WIDGETS) {
3835 Log.d(TAG, "bindAppWidget: " + item);
3836 }
3837 final Workspace workspace = mWorkspace;
3838
3839 LauncherAppWidgetProviderInfo appWidgetInfo =
3840 LauncherModel.getProviderInfo(this, item.providerName);
3841
3842 if (!mIsSafeModeEnabled
3843 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
3844 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
3845 if (appWidgetInfo == null) {
3846 if (DEBUG_WIDGETS) {
3847 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3848 + " belongs to component " + item.providerName
3849 + ", as the povider is null");
3850 }
3851 LauncherModel.deleteItemFromDatabase(this, item);
3852 return;
3853 }
3854 // Note: This assumes that the id remap broadcast is received before this step.
3855 // If that is not the case, the id remap will be ignored and user may see the
3856 // click to setup view.
3857 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
3858 pendingInfo.spanX = item.spanX;
3859 pendingInfo.spanY = item.spanY;
3860 pendingInfo.minSpanX = item.minSpanX;
3861 pendingInfo.minSpanY = item.minSpanY;
3862 Bundle options =
3863 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
3864
3865 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3866 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
3867 newWidgetId, appWidgetInfo, options);
3868
3869 // TODO consider showing a permission dialog when the widget is clicked.
3870 if (!success) {
3871 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3872 if (DEBUG_WIDGETS) {
3873 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3874 + " belongs to component " + item.providerName
3875 + ", as the launcher is unable to bing a new widget id");
3876 }
3877 LauncherModel.deleteItemFromDatabase(this, item);
3878 return;
3879 }
3880
3881 item.appWidgetId = newWidgetId;
3882
3883 // If the widget has a configure activity, it is still needs to set it up, otherwise
3884 // the widget is ready to go.
3885 item.restoreStatus = (appWidgetInfo.configure == null)
3886 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
3887 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3888
3889 LauncherModel.updateItemInDatabase(this, item);
3890 }
3891
3892 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
3893 final int appWidgetId = item.appWidgetId;
3894 if (DEBUG_WIDGETS) {
3895 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
3896 + appWidgetInfo.provider);
3897 }
3898
3899 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3900 } else {
3901 appWidgetInfo = null;
3902 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
3903 mIsSafeModeEnabled);
3904 view.updateIcon(mIconCache);
3905 item.hostView = view;
3906 item.hostView.updateAppWidget(null);
3907 item.hostView.setOnClickListener(this);
3908 }
3909
3910 item.hostView.setTag(item);
3911 item.onBindAppWidget(this);
3912
3913 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
3914 item.cellY, item.spanX, item.spanY, false);
3915 if (!item.isCustomWidget()) {
3916 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3917 }
3918
3919 workspace.requestLayout();
3920
3921 if (DEBUG_WIDGETS) {
3922 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
3923 + (SystemClock.uptimeMillis()-start) + "ms");
3924 }
3925 }
3926
3927 /**
3928 * Restores a pending widget.
3929 *
3930 * @param appWidgetId The app widget id
3931 * @param cellInfo The position on screen where to create the widget.
3932 */
3933 private void completeRestoreAppWidget(final int appWidgetId) {
3934 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
3935 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
3936 Log.e(TAG, "Widget update called, when the widget no longer exists.");
3937 return;
3938 }
3939
3940 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
3941 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
3942
3943 mWorkspace.reinflateWidgetsIfNecessary();
3944 LauncherModel.updateItemInDatabase(this, info);
3945 }
3946
3947 public void onPageBoundSynchronously(int page) {
3948 mSynchronouslyBoundPages.add(page);
3949 }
3950
3951 /**
3952 * Callback saying that there aren't any more items to bind.
3953 *
3954 * Implementation of the method from LauncherModel.Callbacks.
3955 */
3956 public void finishBindingItems() {
3957 Runnable r = new Runnable() {
3958 public void run() {
3959 finishBindingItems();
3960 }
3961 };
3962 if (waitUntilResume(r)) {
3963 return;
3964 }
3965 if (mSavedState != null) {
3966 if (!mWorkspace.hasFocus()) {
3967 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
3968 }
3969 mSavedState = null;
3970 }
3971 mWorkspace.restoreInstanceStateForRemainingPages();
3972 setWorkspaceLoading(false);
3973 sendLoadingCompleteBroadcastIfNecessary();
3974 // If we received the result of any pending adds while the loader was running (e.g. the
3975 // widget configuration forced an orientation change), process them now.
3976 if (sPendingAddItem != null) {
3977 final long screenId = completeAdd(sPendingAddItem);
3978 // TODO: this moves the user to the page where the pending item was added. Ideally,
3979 // the screen would be guaranteed to exist after bind, and the page would be set through
3980 // the workspace restore process.
3981 mWorkspace.post(new Runnable() {
3982 @Override
3983 public void run() {
3984 mWorkspace.snapToScreenId(screenId);
3985 }
3986 });
3987 sPendingAddItem = null;
3988 }
3989 PackageInstallerCompat.getInstance(this).onFinishBind();
3990 if (mLauncherCallbacks != null) {
3991 mLauncherCallbacks.finishBindingItems(false);
3992 }
3993 }
3994
3995 private void sendLoadingCompleteBroadcastIfNecessary() {
3996 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
3997 String permission =
3998 getResources().getString(R.string.receive_first_load_broadcast_permission);
3999 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4000 sendBroadcast(intent, permission);
4001 SharedPreferences.Editor editor = mSharedPrefs.edit();
4002 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4003 editor.apply();
4004 }
4005 }
4006
4007 public boolean isAllAppsButtonRank(int rank) {
4008 if (mHotseat != null) {
4009 return mHotseat.isAllAppsButtonRank(rank);
4010 }
4011 return false;
4012 }
4013
4014 private boolean canRunNewAppsAnimation() {
4015 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4016 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4017 }
4018
4019 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4020 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4021 PropertyValuesHolder.ofFloat("alpha", 1f),
4022 PropertyValuesHolder.ofFloat("scaleX", 1f),
4023 PropertyValuesHolder.ofFloat("scaleY", 1f));
4024 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4025 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4026 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4027 return bounceAnim;
4028 }
4029
4030 public boolean useVerticalBarLayout() {
4031 return LauncherAppState.getInstance().getDynamicGrid().
4032 getDeviceProfile().isVerticalBarLayout();
4033 }
4034
4035 protected Rect getSearchBarBounds() {
4036 return LauncherAppState.getInstance().getDynamicGrid().
4037 getDeviceProfile().getSearchBarBounds();
4038 }
4039
4040 public void bindSearchablesChanged() {
4041 if (mSearchDropTargetBar == null) {
4042 return;
4043 }
4044 if (mQsb != null) {
4045 mSearchDropTargetBar.removeView(mQsb);
4046 mQsb = null;
4047 }
4048 getOrCreateQsbBar();
4049 }
4050
4051 /**
4052 * Add the icons for all apps.
4053 *
4054 * Implementation of the method from LauncherModel.Callbacks.
4055 */
4056 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4057 if (mAppsView != null) {
4058 mAppsView.setApps(apps);
4059 }
4060 if (mAppsCustomizeContent != null) {
4061 mAppsCustomizeContent.onPackagesUpdated(/* refresh */
4062 LauncherModel.getSortedWidgetsAndShortcuts(this, false));
4063 }
4064 if (mLauncherCallbacks != null) {
4065 mLauncherCallbacks.bindAllApplications(apps);
4066 }
4067 }
4068
4069 /**
4070 * A package was updated.
4071 *
4072 * Implementation of the method from LauncherModel.Callbacks.
4073 */
4074 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4075 Runnable r = new Runnable() {
4076 public void run() {
4077 bindAppsUpdated(apps);
4078 }
4079 };
4080 if (waitUntilResume(r)) {
4081 return;
4082 }
4083 if (mAppsView != null) {
4084 mAppsView.updateApps(apps);
4085 }
4086 }
4087
4088 @Override
4089 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4090 Runnable r = new Runnable() {
4091 public void run() {
4092 bindWidgetsRestored(widgets);
4093 }
4094 };
4095 if (waitUntilResume(r)) {
4096 return;
4097 }
4098 mWorkspace.widgetsRestored(widgets);
4099 }
4100
4101 /**
4102 * Some shortcuts were updated in the background.
4103 *
4104 * Implementation of the method from LauncherModel.Callbacks.
4105 */
4106 @Override
4107 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4108 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4109 Runnable r = new Runnable() {
4110 public void run() {
4111 bindShortcutsChanged(updated, removed, user);
4112 }
4113 };
4114 if (waitUntilResume(r)) {
4115 return;
4116 }
4117
4118 if (!updated.isEmpty()) {
4119 mWorkspace.updateShortcuts(updated);
4120 }
4121
4122 if (!removed.isEmpty()) {
4123 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4124 for (ShortcutInfo si : removed) {
4125 removedComponents.add(si.getTargetComponent());
4126 }
4127 mWorkspace.removeItemsByComponentName(removedComponents, user);
4128 // Notify the drag controller
4129 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4130 }
4131 }
4132
4133 /**
4134 * Update the state of a package, typically related to install state.
4135 *
4136 * Implementation of the method from LauncherModel.Callbacks.
4137 */
4138 @Override
4139 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4140 if (mWorkspace != null) {
4141 mWorkspace.updatePackageState(installInfo);
4142 }
4143 }
4144
4145 /**
4146 * Update the label and icon of all the icons in a package
4147 *
4148 * Implementation of the method from LauncherModel.Callbacks.
4149 */
4150 @Override
4151 public void updatePackageBadge(String packageName) {
4152 if (mWorkspace != null) {
4153 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4154 }
4155 }
4156
4157 /**
4158 * A package was uninstalled. We take both the super set of packageNames
4159 * in addition to specific applications to remove, the reason being that
4160 * this can be called when a package is updated as well. In that scenario,
4161 * we only remove specific components from the workspace, where as
4162 * package-removal should clear all items by package name.
4163 *
4164 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4165 * Implementation of the method from LauncherModel.Callbacks.
4166 */
4167 @Override
4168 public void bindComponentsRemoved(final ArrayList<String> packageNames, final ArrayList<AppInfo> appI🔵
4169 Runnable r = new Runnable() {
4170 public void run() {
4171 bindComponentsRemoved(packageNames, appInfos, user, reason);
4172 }
4173 };
4174 if (waitUntilResume(r)) {
4175 return;
4176 }
4177 if (reason == 0) {
4178 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4179 for (AppInfo info : appInfos) {
4180 removedComponents.add(info.componentName);
4181 }
4182 if (!packageNames.isEmpty()) {
4183 mWorkspace.removeItemsByPackageName(packageNames, user);
4184 }
4185 if (!removedComponents.isEmpty()) {
4186 mWorkspace.removeItemsByComponentName(removedComponents, user);
4187 }
4188 // Notify the drag controller
4189 mDragController.onAppsRemoved(packageNames, removedComponents);
4190 } else {
4191 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4192 }
4193 // Update AllApps
4194 if (mAppsView != null) {
4195 mAppsView.removeApps(appInfos);
4196 }
4197 }
4198
4199 /**
4200 * A number of packages were updated.
4201 */
4202 private ArrayList<Object> mWidgetsAndShortcuts;
4203
4204 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4205 public void run() {
4206 bindPackagesUpdated(mWidgetsAndShortcuts);
4207 mWidgetsAndShortcuts = null;
4208 }
4209 };
4210
4211 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4212 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4213 mWidgetsAndShortcuts = widgetsAndShortcuts;
4214 return;
4215 }
4216
4217 // Update the widgets pane
4218 if (mAppsCustomizeContent != null) {
4219 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
4220 }
4221 }
4222
4223 private int mapConfigurationOriActivityInfoOri(int configOri) {
4224 final Display d = getWindowManager().getDefaultDisplay();
4225 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4226 switch (d.getRotation()) {
4227 case Surface.ROTATION_0:
4228 case Surface.ROTATION_180:
4229 // We are currently in the same basic orientation as the natural orientation
4230 naturalOri = configOri;
4231 break;
4232 case Surface.ROTATION_90:
4233 case Surface.ROTATION_270:
4234 // We are currently in the other basic orientation to the natural orientation
4235 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4236 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4237 break;
4238 }
4239
4240 int[] oriMap = {
4241 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4242 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4243 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4244 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4245 };
4246 // Since the map starts at portrait, we need to offset if this device's natural orientation
4247 // is landscape.
4248 int indexOffset = 0;
4249 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4250 indexOffset = 1;
4251 }
4252 return oriMap[(d.getRotation() + indexOffset) % 4];
4253 }
4254
4255 public void lockScreenOrientation() {
4256 if (Utilities.isRotationEnabled(this)) {
4257 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4258 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4259 .getConfiguration().orientation));
4260 } else {
4261 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4262 }
4263 }
4264 }
4265
4266 public void unlockScreenOrientation(boolean immediate) {
4267 if (Utilities.isRotationEnabled(this)) {
4268 if (immediate) {
4269 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4270 } else {
4271 mHandler.postDelayed(new Runnable() {
4272 public void run() {
4273 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4274 }
4275 }, mRestoreScreenOrientationDelay);
4276 }
4277 }
4278 }
4279
4280 protected boolean isLauncherPreinstalled() {
4281 if (mLauncherCallbacks != null) {
4282 return mLauncherCallbacks.isLauncherPreinstalled();
4283 }
4284 PackageManager pm = getPackageManager();
4285 try {
4286 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4287 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4288 return true;
4289 } else {
4290 return false;
4291 }
4292 } catch (NameNotFoundException e) {
4293 e.printStackTrace();
4294 return false;
4295 }
4296 }
4297
4298 /**
4299 * This method indicates whether or not we should suggest default wallpaper dimensions
4300 * when our wallpaper cropper was not yet used to set a wallpaper.
4301 */
4302 protected boolean overrideWallpaperDimensions() {
4303 if (mLauncherCallbacks != null) {
4304 return mLauncherCallbacks.overrideWallpaperDimensions();
4305 }
4306 return true;
4307 }
4308
4309 /**
4310 * To be overridden by subclasses to indicate that there is an activity to launch
4311 * before showing the standard launcher experience.
4312 */
4313 protected boolean hasFirstRunActivity() {
4314 if (mLauncherCallbacks != null) {
4315 return mLauncherCallbacks.hasFirstRunActivity();
4316 }
4317 return false;
4318 }
4319
4320 /**
4321 * To be overridden by subclasses to launch any first run activity
4322 */
4323 protected Intent getFirstRunActivity() {
4324 if (mLauncherCallbacks != null) {
4325 return mLauncherCallbacks.getFirstRunActivity();
4326 }
4327 return null;
4328 }
4329
4330 private boolean shouldRunFirstRunActivity() {
4331 return !ActivityManager.isRunningInTestHarness() &&
4332 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4333 }
4334
4335 protected boolean hasRunFirstRunActivity() {
4336 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4337 }
4338
4339 public boolean showFirstRunActivity() {
4340 if (shouldRunFirstRunActivity() &&
4341 hasFirstRunActivity()) {
4342 Intent firstRunIntent = getFirstRunActivity();
4343 if (firstRunIntent != null) {
4344 startActivity(firstRunIntent);
4345 markFirstRunActivityShown();
4346 return true;
4347 }
4348 }
4349 return false;
4350 }
4351
4352 private void markFirstRunActivityShown() {
4353 SharedPreferences.Editor editor = mSharedPrefs.edit();
4354 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4355 editor.apply();
4356 }
4357
4358 /**
4359 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4360 * screen that must be displayed and dismissed.
4361 */
4362 protected boolean hasDismissableIntroScreen() {
4363 if (mLauncherCallbacks != null) {
4364 return mLauncherCallbacks.hasDismissableIntroScreen();
4365 }
4366 return false;
4367 }
4368
4369 /**
4370 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4371 */
4372 protected View getIntroScreen() {
4373 if (mLauncherCallbacks != null) {
4374 return mLauncherCallbacks.getIntroScreen();
4375 }
4376 return null;
4377 }
4378
4379 /**
4380 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4381 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4382 */
4383 private boolean shouldShowIntroScreen() {
4384 return hasDismissableIntroScreen() &&
4385 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4386 }
4387
4388 protected void showIntroScreen() {
4389 View introScreen = getIntroScreen();
4390 changeWallpaperVisiblity(false);
4391 if (introScreen != null) {
4392 mDragLayer.showOverlayView(introScreen);
4393 }
4394 if (mLauncherOverlayContainer != null) {
4395 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4396 }
4397 }
4398
4399 public void dismissIntroScreen() {
4400 markIntroScreenDismissed();
4401 if (showFirstRunActivity()) {
4402 // We delay hiding the intro view until the first run activity is showing. This
4403 // avoids a blip.
4404 mWorkspace.postDelayed(new Runnable() {
4405 @Override
4406 public void run() {
4407 mDragLayer.dismissOverlayView();
4408 if (mLauncherOverlayContainer != null) {
4409 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4410 }
4411 showFirstRunClings();
4412 }
4413 }, ACTIVITY_START_DELAY);
4414 } else {
4415 mDragLayer.dismissOverlayView();
4416 if (mLauncherOverlayContainer != null) {
4417 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4418 }
4419 showFirstRunClings();
4420 }
4421 changeWallpaperVisiblity(true);
4422 }
4423
4424 private void markIntroScreenDismissed() {
4425 SharedPreferences.Editor editor = mSharedPrefs.edit();
4426 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4427 editor.apply();
4428 }
4429
4430 private void showFirstRunClings() {
4431 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4432 // on the device, then we always show the first run cling experience (or if there is no
4433 // launcher2). Otherwise, we prompt the user upon started for migration
4434 LauncherClings launcherClings = new LauncherClings(this);
4435 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4436 if (mModel.canMigrateFromOldLauncherDb(this)) {
4437 launcherClings.showMigrationCling();
4438 } else {
4439 launcherClings.showLongPressCling(true);
4440 }
4441 }
4442 }
4443
4444 void showWorkspaceSearchAndHotseat() {
4445 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4446 if (mHotseat != null) mHotseat.setAlpha(1f);
4447 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4448 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4449 }
4450
4451 void hideWorkspaceSearchAndHotseat() {
4452 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4453 if (mHotseat != null) mHotseat.setAlpha(0f);
4454 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4455 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4456 }
4457
4458 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4459 // Called from search suggestion, not supported in other profiles.
4460 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4461 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4462 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent, myUser);
4463 if (activityInfo == null) {
4464 return null;
4465 }
4466 return new AppInfo(this, activityInfo, myUser, mIconCache);
4467 }
4468
4469 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4470 Bitmap icon) {
4471 // Called from search suggestion, not supported in other profiles.
4472 return createShortcutDragInfo(shortcutIntent, caption, icon,
4473 UserHandleCompat.myUserHandle());
4474 }
4475
4476 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4477 Bitmap icon, UserHandleCompat user) {
4478 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4479 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4480 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4481 }
4482
4483 protected void moveWorkspaceToDefaultScreen() {
4484 mWorkspace.moveToDefaultScreen(false);
4485 }
4486
4487 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4488 dragView.setTag(dragInfo);
4489 mWorkspace.onExternalDragStartedWithItem(dragView);
4490 mWorkspace.beginExternalDragShared(dragView, source);
4491 }
4492
4493 @Override
4494 public void onPageSwitch(View newPage, int newPageIndex) {
4495 if (mLauncherCallbacks != null) {
4496 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4497 }
4498 }
4499
4500 /**
4501 * Prints out out state for debugging.
4502 */
4503 public void dumpState() {
4504 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4505 Log.d(TAG, "mSavedState=" + mSavedState);
4506 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4507 Log.d(TAG, "mRestoring=" + mRestoring);
4508 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4509 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4510 Log.d(TAG, "sFolders.size=" + sFolders.size());
4511 mModel.dumpState();
4512
4513 if (mAppsCustomizeContent != null) {
4514 mAppsCustomizeContent.dumpState();
4515 }
4516 Log.d(TAG, "END launcher3 dump state");
4517 }
4518
4519 @Override
4520 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4521 super.dump(prefix, fd, writer, args);
4522 synchronized (sDumpLogs) {
4523 writer.println(" ");
4524 writer.println("Debug logs: ");
4525 for (int i = 0; i < sDumpLogs.size(); i++) {
4526 writer.println(" " + sDumpLogs.get(i));
4527 }
4528 }
4529 if (mLauncherCallbacks != null) {
4530 mLauncherCallbacks.dump(prefix, fd, writer, args);
4531 }
4532 }
4533
4534 public static void dumpDebugLogsToConsole() {
4535 if (DEBUG_DUMP_LOG) {
4536 synchronized (sDumpLogs) {
4537 Log.d(TAG, "");
4538 Log.d(TAG, "*********************");
4539 Log.d(TAG, "Launcher debug logs: ");
4540 for (int i = 0; i < sDumpLogs.size(); i++) {
4541 Log.d(TAG, " " + sDumpLogs.get(i));
4542 }
4543 Log.d(TAG, "*********************");
4544 Log.d(TAG, "");
4545 }
4546 }
4547 }
4548
4549 public static void addDumpLog(String tag, String log, boolean debugLog) {
4550 addDumpLog(tag, log, null, debugLog);
4551 }
4552
4553 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4554 if (debugLog) {
4555 if (e != null) {
4556 Log.d(tag, log, e);
4557 } else {
4558 Log.d(tag, log);
4559 }
4560 }
4561 if (DEBUG_DUMP_LOG) {
4562 sDateStamp.setTime(System.currentTimeMillis());
4563 synchronized (sDumpLogs) {
4564 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4565 + (e == null ? "" : (", Exception: " + e)));
4566 }
4567 }
4568 }
4569
4570 public static CustomAppWidget getCustomAppWidget(String name) {
4571 return sCustomAppWidgets.get(name);
4572 }
4573
4574 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4575 return sCustomAppWidgets;
4576 }
4577
4578 public void dumpLogsToLocalData() {
4579 if (DEBUG_DUMP_LOG) {
4580 new AsyncTask<Void, Void, Void>() {
4581 public Void doInBackground(Void ... args) {
4582 boolean success = false;
4583 sDateStamp.setTime(sRunStart);
4584 String FILENAME = sDateStamp.getMonth() + "-"
4585 + sDateStamp.getDay() + "_"
4586 + sDateStamp.getHours() + "-"
4587 + sDateStamp.getMinutes() + "_"
4588 + sDateStamp.getSeconds() + ".txt";
4589
4590 FileOutputStream fos = null;
4591 File outFile = null;
4592 try {
4593 outFile = new File(getFilesDir(), FILENAME);
4594 outFile.createNewFile();
4595 fos = new FileOutputStream(outFile);
4596 } catch (Exception e) {
4597 e.printStackTrace();
4598 }
4599 if (fos != null) {
4600 PrintWriter writer = new PrintWriter(fos);
4601
4602 writer.println(" ");
4603 writer.println("Debug logs: ");
4604 synchronized (sDumpLogs) {
4605 for (int i = 0; i < sDumpLogs.size(); i++) {
4606 writer.println(" " + sDumpLogs.get(i));
4607 }
4608 }
4609 writer.close();
4610 }
4611 try {
4612 if (fos != null) {
4613 fos.close();
4614 success = true;
4615 }
4616 } catch (IOException e) {
4617 e.printStackTrace();
4618 }
4619 return null;
4620 }
4621 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4622 }
4623 }
4624 }
4625
4626 interface LauncherTransitionable {
4627 public abstract View getContent();
4628
4629 public abstract void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
4630
4631 public abstract void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
4632
4633 public abstract void onLauncherTransitionStep(Launcher l, float t);
4634
4635 public abstract void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
4636 }
|